react-native-roster 0.0.0 → 0.1.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 (373) hide show
  1. package/NOTICE.md +29 -0
  2. package/README.md +455 -2
  3. package/dist/src/adapters/rrule/cache.d.ts +15 -0
  4. package/dist/src/adapters/rrule/cache.d.ts.map +1 -0
  5. package/dist/src/adapters/rrule/cache.js +53 -0
  6. package/dist/src/adapters/rrule/cache.js.map +1 -0
  7. package/dist/src/adapters/rrule/envelope.d.ts +3 -0
  8. package/dist/src/adapters/rrule/envelope.d.ts.map +1 -0
  9. package/dist/src/adapters/rrule/envelope.js +9 -0
  10. package/dist/src/adapters/rrule/envelope.js.map +1 -0
  11. package/dist/src/adapters/rrule/expand.d.ts +4 -0
  12. package/dist/src/adapters/rrule/expand.d.ts.map +1 -0
  13. package/dist/src/adapters/rrule/expand.js +92 -0
  14. package/dist/src/adapters/rrule/expand.js.map +1 -0
  15. package/dist/src/adapters/rrule/hash.d.ts +3 -0
  16. package/dist/src/adapters/rrule/hash.d.ts.map +1 -0
  17. package/dist/src/adapters/rrule/hash.js +15 -0
  18. package/dist/src/adapters/rrule/hash.js.map +1 -0
  19. package/dist/src/adapters/rrule/index.d.ts +5 -0
  20. package/dist/src/adapters/rrule/index.d.ts.map +1 -0
  21. package/dist/src/adapters/rrule/index.js +12 -0
  22. package/dist/src/adapters/rrule/index.js.map +1 -0
  23. package/dist/src/adapters/rrule/net.d.ts +6 -0
  24. package/dist/src/adapters/rrule/net.d.ts.map +1 -0
  25. package/dist/src/adapters/rrule/net.js +31 -0
  26. package/dist/src/adapters/rrule/net.js.map +1 -0
  27. package/dist/src/adapters/rrule/occurrences.d.ts +6 -0
  28. package/dist/src/adapters/rrule/occurrences.d.ts.map +1 -0
  29. package/dist/src/adapters/rrule/occurrences.js +252 -0
  30. package/dist/src/adapters/rrule/occurrences.js.map +1 -0
  31. package/dist/src/adapters/rrule/types.d.ts +62 -0
  32. package/dist/src/adapters/rrule/types.d.ts.map +1 -0
  33. package/dist/src/adapters/rrule/types.js +3 -0
  34. package/dist/src/adapters/rrule/types.js.map +1 -0
  35. package/dist/src/adapters/rrule/validate.d.ts +6 -0
  36. package/dist/src/adapters/rrule/validate.d.ts.map +1 -0
  37. package/dist/src/adapters/rrule/validate.js +64 -0
  38. package/dist/src/adapters/rrule/validate.js.map +1 -0
  39. package/dist/src/components/layers/layers.types.d.ts +15 -0
  40. package/dist/src/components/layers/layers.types.d.ts.map +1 -0
  41. package/dist/src/components/layers/layers.types.js +3 -0
  42. package/dist/src/components/layers/layers.types.js.map +1 -0
  43. package/dist/src/components/layers/parts/gap.d.ts +3 -0
  44. package/dist/src/components/layers/parts/gap.d.ts.map +1 -0
  45. package/dist/src/components/layers/parts/gap.js +7 -0
  46. package/dist/src/components/layers/parts/gap.js.map +1 -0
  47. package/dist/src/components/layers/parts/interval.d.ts +3 -0
  48. package/dist/src/components/layers/parts/interval.d.ts.map +1 -0
  49. package/dist/src/components/layers/parts/interval.js +22 -0
  50. package/dist/src/components/layers/parts/interval.js.map +1 -0
  51. package/dist/src/components/layers/utils/styles.d.ts +7 -0
  52. package/dist/src/components/layers/utils/styles.d.ts.map +1 -0
  53. package/dist/src/components/layers/utils/styles.js +24 -0
  54. package/dist/src/components/layers/utils/styles.js.map +1 -0
  55. package/dist/src/components/primitives/press-point.d.ts +3 -0
  56. package/dist/src/components/primitives/press-point.d.ts.map +1 -0
  57. package/dist/src/components/primitives/press-point.js +7 -0
  58. package/dist/src/components/primitives/press-point.js.map +1 -0
  59. package/dist/src/components/primitives/press-point.types.d.ts +14 -0
  60. package/dist/src/components/primitives/press-point.types.d.ts.map +1 -0
  61. package/dist/src/components/primitives/press-point.types.js +3 -0
  62. package/dist/src/components/primitives/press-point.types.js.map +1 -0
  63. package/dist/src/components/primitives/press-point.web.d.ts +3 -0
  64. package/dist/src/components/primitives/press-point.web.d.ts.map +1 -0
  65. package/dist/src/components/primitives/press-point.web.js +17 -0
  66. package/dist/src/components/primitives/press-point.web.js.map +1 -0
  67. package/dist/src/components/primitives/region-style.d.ts +9 -0
  68. package/dist/src/components/primitives/region-style.d.ts.map +1 -0
  69. package/dist/src/components/primitives/region-style.js +20 -0
  70. package/dist/src/components/primitives/region-style.js.map +1 -0
  71. package/dist/src/components/roster/body-layout.d.ts +9 -0
  72. package/dist/src/components/roster/body-layout.d.ts.map +1 -0
  73. package/dist/src/components/roster/body-layout.js +9 -0
  74. package/dist/src/components/roster/body-layout.js.map +1 -0
  75. package/dist/src/components/roster/index.d.ts +3 -0
  76. package/dist/src/components/roster/index.d.ts.map +1 -0
  77. package/dist/src/components/roster/index.js +40 -0
  78. package/dist/src/components/roster/index.js.map +1 -0
  79. package/dist/src/components/roster/lanes/interval-hover.d.ts +3 -0
  80. package/dist/src/components/roster/lanes/interval-hover.d.ts.map +1 -0
  81. package/dist/src/components/roster/lanes/interval-hover.js +7 -0
  82. package/dist/src/components/roster/lanes/interval-hover.js.map +1 -0
  83. package/dist/src/components/roster/lanes/interval-hover.types.d.ts +15 -0
  84. package/dist/src/components/roster/lanes/interval-hover.types.d.ts.map +1 -0
  85. package/dist/src/components/roster/lanes/interval-hover.types.js +3 -0
  86. package/dist/src/components/roster/lanes/interval-hover.types.js.map +1 -0
  87. package/dist/src/components/roster/lanes/interval-hover.web.d.ts +3 -0
  88. package/dist/src/components/roster/lanes/interval-hover.web.d.ts.map +1 -0
  89. package/dist/src/components/roster/lanes/interval-hover.web.js +20 -0
  90. package/dist/src/components/roster/lanes/interval-hover.web.js.map +1 -0
  91. package/dist/src/components/roster/lanes/lane.d.ts +10 -0
  92. package/dist/src/components/roster/lanes/lane.d.ts.map +1 -0
  93. package/dist/src/components/roster/lanes/lane.js +32 -0
  94. package/dist/src/components/roster/lanes/lane.js.map +1 -0
  95. package/dist/src/components/roster/lanes/parts/incomplete.d.ts +8 -0
  96. package/dist/src/components/roster/lanes/parts/incomplete.d.ts.map +1 -0
  97. package/dist/src/components/roster/lanes/parts/incomplete.js +30 -0
  98. package/dist/src/components/roster/lanes/parts/incomplete.js.map +1 -0
  99. package/dist/src/components/roster/lanes/parts/lane-label.d.ts +3 -0
  100. package/dist/src/components/roster/lanes/parts/lane-label.d.ts.map +1 -0
  101. package/dist/src/components/roster/lanes/parts/lane-label.js +18 -0
  102. package/dist/src/components/roster/lanes/parts/lane-label.js.map +1 -0
  103. package/dist/src/components/roster/layout.d.ts +17 -0
  104. package/dist/src/components/roster/layout.d.ts.map +1 -0
  105. package/dist/src/components/roster/layout.js +24 -0
  106. package/dist/src/components/roster/layout.js.map +1 -0
  107. package/dist/src/components/roster/parts/body.d.ts +7 -0
  108. package/dist/src/components/roster/parts/body.d.ts.map +1 -0
  109. package/dist/src/components/roster/parts/body.js +14 -0
  110. package/dist/src/components/roster/parts/body.js.map +1 -0
  111. package/dist/src/components/roster/parts/corner.d.ts +2 -0
  112. package/dist/src/components/roster/parts/corner.d.ts.map +1 -0
  113. package/dist/src/components/roster/parts/corner.js +7 -0
  114. package/dist/src/components/roster/parts/corner.js.map +1 -0
  115. package/dist/src/components/roster/parts/empty.d.ts +2 -0
  116. package/dist/src/components/roster/parts/empty.d.ts.map +1 -0
  117. package/dist/src/components/roster/parts/empty.js +9 -0
  118. package/dist/src/components/roster/parts/empty.js.map +1 -0
  119. package/dist/src/components/roster/parts/grid.d.ts +3 -0
  120. package/dist/src/components/roster/parts/grid.d.ts.map +1 -0
  121. package/dist/src/components/roster/parts/grid.js +16 -0
  122. package/dist/src/components/roster/parts/grid.js.map +1 -0
  123. package/dist/src/components/roster/parts/header-cell.d.ts +3 -0
  124. package/dist/src/components/roster/parts/header-cell.d.ts.map +1 -0
  125. package/dist/src/components/roster/parts/header-cell.js +9 -0
  126. package/dist/src/components/roster/parts/header-cell.js.map +1 -0
  127. package/dist/src/components/roster/parts/header.d.ts +3 -0
  128. package/dist/src/components/roster/parts/header.d.ts.map +1 -0
  129. package/dist/src/components/roster/parts/header.js +13 -0
  130. package/dist/src/components/roster/parts/header.js.map +1 -0
  131. package/dist/src/components/roster/parts/lane-label-column.d.ts +3 -0
  132. package/dist/src/components/roster/parts/lane-label-column.d.ts.map +1 -0
  133. package/dist/src/components/roster/parts/lane-label-column.js +18 -0
  134. package/dist/src/components/roster/parts/lane-label-column.js.map +1 -0
  135. package/dist/src/components/roster/parts/lane-list.d.ts +7 -0
  136. package/dist/src/components/roster/parts/lane-list.d.ts.map +1 -0
  137. package/dist/src/components/roster/parts/lane-list.js +25 -0
  138. package/dist/src/components/roster/parts/lane-list.js.map +1 -0
  139. package/dist/src/components/roster/roster.types.d.ts +145 -0
  140. package/dist/src/components/roster/roster.types.d.ts.map +1 -0
  141. package/dist/src/components/roster/roster.types.js +3 -0
  142. package/dist/src/components/roster/roster.types.js.map +1 -0
  143. package/dist/src/components/roster/use-roster.d.ts +3 -0
  144. package/dist/src/components/roster/use-roster.d.ts.map +1 -0
  145. package/dist/src/components/roster/use-roster.js +106 -0
  146. package/dist/src/components/roster/use-roster.js.map +1 -0
  147. package/dist/src/components/roster/utils/body-content-key.d.ts +3 -0
  148. package/dist/src/components/roster/utils/body-content-key.d.ts.map +1 -0
  149. package/dist/src/components/roster/utils/body-content-key.js +29 -0
  150. package/dist/src/components/roster/utils/body-content-key.js.map +1 -0
  151. package/dist/src/components/roster/utils/ticks.d.ts +4 -0
  152. package/dist/src/components/roster/utils/ticks.d.ts.map +1 -0
  153. package/dist/src/components/roster/utils/ticks.js +60 -0
  154. package/dist/src/components/roster/utils/ticks.js.map +1 -0
  155. package/dist/src/components/schedule/days/day.d.ts +20 -0
  156. package/dist/src/components/schedule/days/day.d.ts.map +1 -0
  157. package/dist/src/components/schedule/days/day.js +17 -0
  158. package/dist/src/components/schedule/days/day.js.map +1 -0
  159. package/dist/src/components/schedule/days/header-layout.d.ts +9 -0
  160. package/dist/src/components/schedule/days/header-layout.d.ts.map +1 -0
  161. package/dist/src/components/schedule/days/header-layout.js +9 -0
  162. package/dist/src/components/schedule/days/header-layout.js.map +1 -0
  163. package/dist/src/components/schedule/days/layout.d.ts +18 -0
  164. package/dist/src/components/schedule/days/layout.d.ts.map +1 -0
  165. package/dist/src/components/schedule/days/layout.js +9 -0
  166. package/dist/src/components/schedule/days/layout.js.map +1 -0
  167. package/dist/src/components/schedule/days/parts/column.d.ts +3 -0
  168. package/dist/src/components/schedule/days/parts/column.d.ts.map +1 -0
  169. package/dist/src/components/schedule/days/parts/column.js +28 -0
  170. package/dist/src/components/schedule/days/parts/column.js.map +1 -0
  171. package/dist/src/components/schedule/days/parts/day-header.d.ts +5 -0
  172. package/dist/src/components/schedule/days/parts/day-header.d.ts.map +1 -0
  173. package/dist/src/components/schedule/days/parts/day-header.js +10 -0
  174. package/dist/src/components/schedule/days/parts/day-header.js.map +1 -0
  175. package/dist/src/components/schedule/days/parts/grid.d.ts +3 -0
  176. package/dist/src/components/schedule/days/parts/grid.d.ts.map +1 -0
  177. package/dist/src/components/schedule/days/parts/grid.js +14 -0
  178. package/dist/src/components/schedule/days/parts/grid.js.map +1 -0
  179. package/dist/src/components/schedule/days/parts/now-line.d.ts +5 -0
  180. package/dist/src/components/schedule/days/parts/now-line.d.ts.map +1 -0
  181. package/dist/src/components/schedule/days/parts/now-line.js +9 -0
  182. package/dist/src/components/schedule/days/parts/now-line.js.map +1 -0
  183. package/dist/src/components/schedule/days/parts/skipped-date.d.ts +4 -0
  184. package/dist/src/components/schedule/days/parts/skipped-date.d.ts.map +1 -0
  185. package/dist/src/components/schedule/days/parts/skipped-date.js +17 -0
  186. package/dist/src/components/schedule/days/parts/skipped-date.js.map +1 -0
  187. package/dist/src/components/schedule/days/parts/transition.d.ts +3 -0
  188. package/dist/src/components/schedule/days/parts/transition.d.ts.map +1 -0
  189. package/dist/src/components/schedule/days/parts/transition.js +42 -0
  190. package/dist/src/components/schedule/days/parts/transition.js.map +1 -0
  191. package/dist/src/components/schedule/index.d.ts +3 -0
  192. package/dist/src/components/schedule/index.d.ts.map +1 -0
  193. package/dist/src/components/schedule/index.js +44 -0
  194. package/dist/src/components/schedule/index.js.map +1 -0
  195. package/dist/src/components/schedule/layout.d.ts +15 -0
  196. package/dist/src/components/schedule/layout.d.ts.map +1 -0
  197. package/dist/src/components/schedule/layout.js +10 -0
  198. package/dist/src/components/schedule/layout.js.map +1 -0
  199. package/dist/src/components/schedule/parts/gutter.d.ts +3 -0
  200. package/dist/src/components/schedule/parts/gutter.d.ts.map +1 -0
  201. package/dist/src/components/schedule/parts/gutter.js +9 -0
  202. package/dist/src/components/schedule/parts/gutter.js.map +1 -0
  203. package/dist/src/components/schedule/parts/incomplete.d.ts +6 -0
  204. package/dist/src/components/schedule/parts/incomplete.d.ts.map +1 -0
  205. package/dist/src/components/schedule/parts/incomplete.js +9 -0
  206. package/dist/src/components/schedule/parts/incomplete.js.map +1 -0
  207. package/dist/src/components/schedule/schedule.types.d.ts +109 -0
  208. package/dist/src/components/schedule/schedule.types.d.ts.map +1 -0
  209. package/dist/src/components/schedule/schedule.types.js +3 -0
  210. package/dist/src/components/schedule/schedule.types.js.map +1 -0
  211. package/dist/src/components/schedule/use-schedule-viewport.d.ts +7 -0
  212. package/dist/src/components/schedule/use-schedule-viewport.d.ts.map +1 -0
  213. package/dist/src/components/schedule/use-schedule-viewport.js +15 -0
  214. package/dist/src/components/schedule/use-schedule-viewport.js.map +1 -0
  215. package/dist/src/components/schedule/use-schedule.d.ts +3 -0
  216. package/dist/src/components/schedule/use-schedule.d.ts.map +1 -0
  217. package/dist/src/components/schedule/use-schedule.js +71 -0
  218. package/dist/src/components/schedule/use-schedule.js.map +1 -0
  219. package/dist/src/components/schedule/utils/days.d.ts +17 -0
  220. package/dist/src/components/schedule/utils/days.d.ts.map +1 -0
  221. package/dist/src/components/schedule/utils/days.js +49 -0
  222. package/dist/src/components/schedule/utils/days.js.map +1 -0
  223. package/dist/src/core/axis.d.ts +18 -0
  224. package/dist/src/core/axis.d.ts.map +1 -0
  225. package/dist/src/core/axis.js +58 -0
  226. package/dist/src/core/axis.js.map +1 -0
  227. package/dist/src/core/cache.d.ts +29 -0
  228. package/dist/src/core/cache.d.ts.map +1 -0
  229. package/dist/src/core/cache.js +33 -0
  230. package/dist/src/core/cache.js.map +1 -0
  231. package/dist/src/core/columns.d.ts +3 -0
  232. package/dist/src/core/columns.d.ts.map +1 -0
  233. package/dist/src/core/columns.js +45 -0
  234. package/dist/src/core/columns.js.map +1 -0
  235. package/dist/src/core/coverage.d.ts +3 -0
  236. package/dist/src/core/coverage.d.ts.map +1 -0
  237. package/dist/src/core/coverage.js +60 -0
  238. package/dist/src/core/coverage.js.map +1 -0
  239. package/dist/src/core/flag.d.ts +3 -0
  240. package/dist/src/core/flag.d.ts.map +1 -0
  241. package/dist/src/core/flag.js +17 -0
  242. package/dist/src/core/flag.js.map +1 -0
  243. package/dist/src/core/hash.d.ts +5 -0
  244. package/dist/src/core/hash.d.ts.map +1 -0
  245. package/dist/src/core/hash.js +29 -0
  246. package/dist/src/core/hash.js.map +1 -0
  247. package/dist/src/core/hit-test.d.ts +6 -0
  248. package/dist/src/core/hit-test.d.ts.map +1 -0
  249. package/dist/src/core/hit-test.js +23 -0
  250. package/dist/src/core/hit-test.js.map +1 -0
  251. package/dist/src/core/index.d.ts +12 -0
  252. package/dist/src/core/index.d.ts.map +1 -0
  253. package/dist/src/core/index.js +30 -0
  254. package/dist/src/core/index.js.map +1 -0
  255. package/dist/src/core/layout.d.ts +3 -0
  256. package/dist/src/core/layout.d.ts.map +1 -0
  257. package/dist/src/core/layout.js +96 -0
  258. package/dist/src/core/layout.js.map +1 -0
  259. package/dist/src/core/order.d.ts +7 -0
  260. package/dist/src/core/order.d.ts.map +1 -0
  261. package/dist/src/core/order.js +12 -0
  262. package/dist/src/core/order.js.map +1 -0
  263. package/dist/src/core/scale.d.ts +9 -0
  264. package/dist/src/core/scale.d.ts.map +1 -0
  265. package/dist/src/core/scale.js +41 -0
  266. package/dist/src/core/scale.js.map +1 -0
  267. package/dist/src/core/snap.d.ts +10 -0
  268. package/dist/src/core/snap.d.ts.map +1 -0
  269. package/dist/src/core/snap.js +43 -0
  270. package/dist/src/core/snap.js.map +1 -0
  271. package/dist/src/core/spans.d.ts +3 -0
  272. package/dist/src/core/spans.d.ts.map +1 -0
  273. package/dist/src/core/spans.js +60 -0
  274. package/dist/src/core/spans.js.map +1 -0
  275. package/dist/src/core/types.d.ts +104 -0
  276. package/dist/src/core/types.d.ts.map +1 -0
  277. package/dist/src/core/types.js +3 -0
  278. package/dist/src/core/types.js.map +1 -0
  279. package/dist/src/core/zone.d.ts +10 -0
  280. package/dist/src/core/zone.d.ts.map +1 -0
  281. package/dist/src/core/zone.js +103 -0
  282. package/dist/src/core/zone.js.map +1 -0
  283. package/dist/src/index.d.ts +29 -0
  284. package/dist/src/index.d.ts.map +1 -0
  285. package/dist/src/index.js +67 -0
  286. package/dist/src/index.js.map +1 -0
  287. package/dist/src/nativewind/index.d.ts +59 -0
  288. package/dist/src/nativewind/index.d.ts.map +1 -0
  289. package/dist/src/nativewind/index.js +24 -0
  290. package/dist/src/nativewind/index.js.map +1 -0
  291. package/docs/migrations.md +61 -0
  292. package/llms.txt +498 -0
  293. package/package.json +155 -8
  294. package/src/adapters/rrule/README.md +41 -0
  295. package/src/adapters/rrule/cache.ts +50 -0
  296. package/src/adapters/rrule/envelope.ts +7 -0
  297. package/src/adapters/rrule/expand.ts +105 -0
  298. package/src/adapters/rrule/hash.ts +13 -0
  299. package/src/adapters/rrule/index.ts +11 -0
  300. package/src/adapters/rrule/net.ts +30 -0
  301. package/src/adapters/rrule/occurrences.ts +288 -0
  302. package/src/adapters/rrule/types.ts +52 -0
  303. package/src/adapters/rrule/validate.ts +78 -0
  304. package/src/components/layers/README.md +15 -0
  305. package/src/components/layers/layers.types.ts +6 -0
  306. package/src/components/layers/parts/gap.tsx +5 -0
  307. package/src/components/layers/parts/interval.tsx +24 -0
  308. package/src/components/layers/utils/styles.ts +24 -0
  309. package/src/components/primitives/README.md +12 -0
  310. package/src/components/primitives/press-point.tsx +5 -0
  311. package/src/components/primitives/press-point.types.ts +5 -0
  312. package/src/components/primitives/press-point.web.tsx +19 -0
  313. package/src/components/primitives/region-style.ts +21 -0
  314. package/src/components/roster/README.md +29 -0
  315. package/src/components/roster/body-layout.tsx +32 -0
  316. package/src/components/roster/index.tsx +108 -0
  317. package/src/components/roster/lanes/interval-hover.tsx +5 -0
  318. package/src/components/roster/lanes/interval-hover.types.ts +11 -0
  319. package/src/components/roster/lanes/interval-hover.web.tsx +19 -0
  320. package/src/components/roster/lanes/lane.tsx +93 -0
  321. package/src/components/roster/lanes/parts/incomplete.tsx +44 -0
  322. package/src/components/roster/lanes/parts/lane-label.tsx +41 -0
  323. package/src/components/roster/layout.tsx +68 -0
  324. package/src/components/roster/parts/body.tsx +24 -0
  325. package/src/components/roster/parts/corner.tsx +3 -0
  326. package/src/components/roster/parts/empty.tsx +5 -0
  327. package/src/components/roster/parts/grid.tsx +26 -0
  328. package/src/components/roster/parts/header-cell.tsx +10 -0
  329. package/src/components/roster/parts/header.tsx +23 -0
  330. package/src/components/roster/parts/lane-label-column.tsx +31 -0
  331. package/src/components/roster/parts/lane-list.tsx +74 -0
  332. package/src/components/roster/roster.types.ts +156 -0
  333. package/src/components/roster/use-roster.ts +133 -0
  334. package/src/components/roster/utils/body-content-key.ts +46 -0
  335. package/src/components/roster/utils/ticks.ts +62 -0
  336. package/src/components/schedule/README.md +23 -0
  337. package/src/components/schedule/days/day.tsx +89 -0
  338. package/src/components/schedule/days/header-layout.tsx +12 -0
  339. package/src/components/schedule/days/layout.tsx +38 -0
  340. package/src/components/schedule/days/parts/column.tsx +59 -0
  341. package/src/components/schedule/days/parts/day-header.tsx +18 -0
  342. package/src/components/schedule/days/parts/grid.tsx +21 -0
  343. package/src/components/schedule/days/parts/now-line.tsx +11 -0
  344. package/src/components/schedule/days/parts/skipped-date.tsx +22 -0
  345. package/src/components/schedule/days/parts/transition.tsx +70 -0
  346. package/src/components/schedule/index.tsx +108 -0
  347. package/src/components/schedule/layout.tsx +42 -0
  348. package/src/components/schedule/parts/gutter.tsx +16 -0
  349. package/src/components/schedule/parts/incomplete.tsx +13 -0
  350. package/src/components/schedule/schedule.types.ts +103 -0
  351. package/src/components/schedule/use-schedule-viewport.ts +13 -0
  352. package/src/components/schedule/use-schedule.ts +75 -0
  353. package/src/components/schedule/utils/days.ts +54 -0
  354. package/src/core/README.md +25 -0
  355. package/src/core/axis.ts +58 -0
  356. package/src/core/cache.ts +38 -0
  357. package/src/core/columns.ts +49 -0
  358. package/src/core/coverage.ts +56 -0
  359. package/src/core/flag.ts +14 -0
  360. package/src/core/hash.ts +28 -0
  361. package/src/core/hit-test.ts +27 -0
  362. package/src/core/index.ts +34 -0
  363. package/src/core/layout.ts +101 -0
  364. package/src/core/order.ts +18 -0
  365. package/src/core/scale.ts +45 -0
  366. package/src/core/snap.ts +52 -0
  367. package/src/core/spans.ts +58 -0
  368. package/src/core/types.ts +92 -0
  369. package/src/core/zone.ts +100 -0
  370. package/src/index.ts +52 -0
  371. package/src/nativewind/README.md +14 -0
  372. package/src/nativewind/index.ts +30 -0
  373. package/index.js +0 -4
package/llms.txt ADDED
@@ -0,0 +1,498 @@
1
+ # react-native-roster
2
+
3
+ > React Native read surfaces over layered absolute intervals with provenance.
4
+ > Roster projects many lanes horizontally; Schedule projects one lane into day
5
+ > columns. The core owns geometry, coverage, axis math, and caches. Adapters own
6
+ > upstream conversion and recurrence. The gallery and integration guides describe
7
+ > the implemented surface; package distribution status is in README.md.
8
+
9
+ This file is for an agent integrating the package into a consumer app. Follow the
10
+ setup, contract, and cache rules below. You do not need the repository's issues
11
+ to render a roster. README.md is the human quick start; AGENTS.md is for modifying
12
+ this repository. The demo is the working integration example, not a dependency.
13
+
14
+ ## Setup and first render
15
+
16
+ Use Bun 1.4.2 and Node 22 for this checkout. Run at repository root:
17
+
18
+ ```sh
19
+ bun install --ignore-scripts --frozen-lockfile
20
+ bun run build
21
+ bun run demo:web
22
+ ```
23
+
24
+ The root install also installs demo dependencies. Use `EXPO_OFFLINE=1 bun run
25
+ demo:web` if Expo cannot write its online dependency-validation cache. To consume from another app,
26
+ `bun add react-native-roster`; for an unreleased checkout, run
27
+ `bun pm pack --destination .cache` here, then `bun add` the actual absolute
28
+ tarball path in your app. Add `@legendapp/list` and install the SDK's
29
+ compatible Reanimated with `bunx expo install react-native-reanimated --bun`.
30
+ Keep React, React Native, and Expo aligned with the app's SDK. The demo uses Expo
31
+ 54, React 19.1, RN 0.81.5, LegendList 2.0.19, and Reanimated 3.19.5. NativeWind is
32
+ an optional peer (>=4.1); import react-native-roster/nativewind once at the app
33
+ root to enable className props on Roster and Schedule. Web needs compatible
34
+ React DOM and React Native Web. See demo/babel.config.js for workspace presets.
35
+
36
+ Peer ranges: React >=18.2, React Native >=0.74, Expo >=51, LegendList >=2.
37
+ Reanimated >=3.19 is optional for core-only installs and required for Roster.
38
+ Tested dependency versions are not proof of every combination in those ranges.
39
+
40
+ Save this as a screen component, or as demo/app/gallery/readme-example.tsx with
41
+ `export default function Example` for Expo Router, and visit /gallery/readme-example:
42
+
43
+ ```tsx
44
+ import { Roster } from 'react-native-roster';
45
+ import type { Lane } from 'react-native-roster/core';
46
+
47
+ const lanes: Lane[] = [{
48
+ id: 'one', label: 'Lane one', layers: [{
49
+ id: 'open', role: 'availability', z: 0, style: { color: '#4f9478' },
50
+ intervals: [{
51
+ start: Date.UTC(2024, 0, 1, 9), end: Date.UTC(2024, 0, 1, 17),
52
+ sources: [{ kind: 'table', id: 'row-one' }],
53
+ }],
54
+ }],
55
+ }];
56
+ export function Example() {
57
+ return <Roster lanes={lanes} style={{ height: 480, flex: undefined }}
58
+ windowSpec={{ span: 'week', anchorDate: '2024-01-01', timezone: 'UTC' }}
59
+ onIntervalPress={(rect, lane) => console.log(lane.id, rect.sources)} />;
60
+ }
61
+ ```
62
+
63
+ Expect a green 09:00 to 17:00 interval on January 1, 2024. Scroll horizontally to
64
+ see the time range. A bounded viewport is required. Remove the scratch route
65
+ when finished. In a controlled app, own WindowSpec in your screen's hook; update
66
+ it in navigation handlers, and prepare adapter output for windowFor(spec).
67
+
68
+ ## Vocabulary
69
+
70
+ | Term | Meaning |
71
+ | --- | --- |
72
+ | roster | Whole read surface: shared time axis, lanes, and layers. |
73
+ | lane | One person or resource row containing layers and display metadata. |
74
+ | layer | Ordered intervals and gaps with role, z-order, and style. |
75
+ | interval | Covered end-exclusive absolute time with sources. |
76
+ | gap | Removed end-exclusive absolute time with sources; survives complete subtraction. |
77
+ | source | Provenance identity (kind, id); optional label is display metadata. |
78
+ | window | Bare absolute { start, end }; no span or resolution. |
79
+ | projection | Horizontal elapsed time or day columns using wall-clock hours. |
80
+ | adapter | External conversion from upstream data to lanes and layers. |
81
+ | schedule | One lane projected into days across and hours down. |
82
+
83
+ Use these words in integration code. The LayerRole strings availability and
84
+ booking, and their coverage-field names below, are exact contract identifiers.
85
+
86
+ ## Data contract
87
+
88
+ ```ts
89
+ type Source = { kind: string; id: string; label?: string };
90
+ type Interval = { start: number; end: number; sources: Source[] };
91
+ type Gap = { start: number; end: number; sources: Source[] };
92
+ type LayerRole = 'availability' | 'booking' | 'custom';
93
+ type LayerStyle = { color: string; highlightColor?: string; inset?: number; opacity?: number };
94
+ type Layer = {
95
+ id: string; role: LayerRole; z: number; style: LayerStyle;
96
+ intervals: Interval[]; gaps?: Gap[]; label?: string;
97
+ };
98
+ type LaneFlag = 'none' | 'never-set' | 'empty-in-window';
99
+ type Lane = {
100
+ id: string; version?: string | number; label: string; timezone?: string;
101
+ flag?: LaneFlag; complete?: boolean; layers: Layer[]; meta?: unknown;
102
+ };
103
+ type Window = { start: number; end: number };
104
+ type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
105
+ type Span = 'day' | 'week' | 'month';
106
+ type WindowSpec =
107
+ | { span: Span; anchorDate: string; wkst?: Weekday; timezone: string }
108
+ | { span: 'custom'; window: Window; timezone: string };
109
+ type Transition = { at: number; deltaMinutes: number };
110
+ type DayColumn = {
111
+ start: number; end: number; localDate: string; label: string; transitions: Transition[];
112
+ };
113
+ type Projection =
114
+ | { orientation: 'horizontal'; viewTimezone: string; pxPerMinute: number; rowHeight: number }
115
+ | { orientation: 'columns'; viewTimezone: string; pxPerHour: number; columnWidth: number; days: DayColumn[] };
116
+ type Rect = {
117
+ x: number; y: number; width: number; height: number; layerId: string;
118
+ z: number; sources: Source[]; column?: number;
119
+ };
120
+ type Coverage = {
121
+ availabilityMinutes: number; bookingMinutes: number; availabilityMinusBookingMinutes: number;
122
+ };
123
+ type LaneGeometry = {
124
+ laneId: string; rects: Rect[]; gapRects: Rect[]; coverage: Coverage; flag: LaneFlag;
125
+ };
126
+ ```
127
+
128
+ All bounds are integer epoch milliseconds, [start, end). Preserve exact times;
129
+ minuteStep is only for ticks and pointer snapping. Source equality uses both
130
+ kind and id, not reference or label. Distinct labels do not create distinct
131
+ sources. Use stable lane ids and unique layer ids within a lane.
132
+
133
+ Role determines union coverage, not z-order. availabilityMinusBookingMinutes
134
+ subtracts the intersection of the two role unions. Gaps are not subtracted again;
135
+ the adapter must already have netted intervals. Layout splits source sets exactly:
136
+ A 09:00 to 11:00 and B 10:00 to 12:00 gives {A}, {A,B}, {B}.
137
+
138
+ Only upstream knowledge justifies never-set. With no explicit flag, intervals
139
+ outside the requested window infer empty-in-window; all other cases infer none.
140
+ complete defaults to true. Set false for capped or partially fetched output and
141
+ show the incompleteLabel; do not describe incomplete empty time as definitive.
142
+
143
+ ## Entry points
144
+
145
+ | Import | Runtime exports and purpose |
146
+ | --- | --- |
147
+ | react-native-roster | Roster, Schedule, useRoster, useSchedule, default slot components, and core re-exports. |
148
+ | react-native-roster/core | layoutLane, coverageFor, flagFor, windowFor, prev, next, today, dayColumnsFor, timeAtX, timeAtY, snapToStep, byLabel, byCoverage, and core counters/caches. |
149
+ | react-native-roster/rrule | expandRuleSet, envelopeFor, expandStats, resetExpandStats, clearExpandCache; RuleSet, RosterRule, RosterDate, ExpandOptions, ExpandResult, and ExpandStats types. |
150
+ | react-native-roster/nativewind | Side-effect registration of Roster and Schedule with NativeWind's cssInterop, plus the registered Roster and Schedule and the rosterClassNames/scheduleClassNames mappings. Imports the optional nativewind peer. |
151
+
152
+ No default exports. Root exports renderer inputs/models/slot input types as well as
153
+ core types. Core imports standard JS and Intl only. Only /rrule imports
154
+ rrule-temporal 1.6.0 and @js-temporal/polyfill 0.5.1. Those are installed runtime
155
+ dependencies but cannot enter root/core bundles through library imports.
156
+
157
+ Metro selects TS source via exports' react-native condition. Keep package exports
158
+ enabled and one React copy in a workspace. Other resolvers load built CommonJS
159
+ from dist/src; declarations use the types condition. Build before Node export
160
+ tests. Root needs a native renderer or web bundler; direct Node use of root is
161
+ not a substitute for a React Native app. Core and /rrule work in Node/Bun.
162
+
163
+ ## 0.1.0 slot prop rename
164
+
165
+ 0.1.0 renamed every input-bearing render-function Zone prop to a Component prop;
166
+ use the current names below instead of the 0.0.x props. Singleton emptyZone and
167
+ cornerZone keep their names and now take nodes. See [docs/migrations.md](./docs/migrations.md).
168
+ Zone props are ReactNode.
169
+ Component props are ComponentType<Input>.
170
+
171
+ ## Renderer contracts
172
+
173
+ Roster required props: lanes: Lane[], windowSpec: WindowSpec.
174
+ Optional: minuteStep (60), rowHeight (48), pxPerMinute (minimum 0.5, fits viewport),
175
+ sortLanes (byLabel), highlightSource, onNavigate, onIntervalPress, onGapPress,
176
+ onCellPress, onIntervalHover (web only), style, headerStyle, laneLabelColumnStyle,
177
+ bodyStyle, laneLabelWidth (180), neverSetLabel, incompleteLabel, and zones below.
178
+ Each style prop has a className twin (className, headerClassName,
179
+ laneLabelColumnClassName, bodyClassName) resolved only after importing
180
+ react-native-roster/nativewind. A class entry replaces the region's default
181
+ paint (background and border colors) and keeps its structure, because React
182
+ Native Web renders object styles inline where they would beat any class. Labels default to "No availability set" and "Availability may
183
+ be incomplete". Supply a bounded height; Roster waits for viewport measurement
184
+ before mounting LegendList. Reanimated keeps horizontal and vertical offsets
185
+ outside React state.
186
+
187
+ useRoster returns window, projection, orderedLanes, coverage, laneState,
188
+ geometryFor(lane), scroll, press(lane,x,y), ticks, contentWidth, viewport,
189
+ onLayout, navigate, and status ('empty' or 'ready'). There is no visibleRange
190
+ field in the shipped public model. Custom layouts must wire onLayout. Custom
191
+ bodies compute geometry only for mounted lanes, preserving virtualization.
192
+ A zero-duration window has contentWidth=0 and renders no Roster body. Positive
193
+ windows use their exact elapsed duration, including sub-minute spans. Cell times
194
+ are clamped up to window.start and rejected at or beyond window.end.
195
+
196
+ Schedule required props: lane: Lane, windowSpec: WindowSpec & {span:'day'|'week'}.
197
+ Optional: minuteStep (60), pxPerHour (48, positive finite), highlightSource,
198
+ onNavigate, onIntervalPress, onGapPress, onCellPress, style, headerStyle,
199
+ gutterStyle, daysStyle, incompleteLabel, and zones below. Class twins are
200
+ className, headerClassName, gutterClassName, and daysClassName. No month/custom
201
+ spec, sort, or interval-hover prop.
202
+
203
+ useSchedule returns window, days, projection, geometry, now (epoch ms or null),
204
+ press(columnIndex,x,y), and status:'ready'. The standalone hook uses a 280 px grid.
205
+ The chassis measures width, reserves a 48 px gutter, and fits all actual day
206
+ columns without horizontal scrolling; the body scrolls vertically. now updates
207
+ every minute and is null outside the selected window.
208
+
209
+ Props ending in Component accept ComponentType<Input>, mounted as React elements.
210
+ Props ending in Zone accept ReactNode; pass null to suppress a singleton default.
211
+ Define component types at module scope; hooks and class components work normally.
212
+ Use exported defaults inside replacements to preserve geometry, scrolling, and
213
+ interaction. Input types, including HeaderCellInput, are exported from root.
214
+ Mount a React context provider above Roster for extra consumer data, and read it with useContext in a module-scope slot component.
215
+ Avoid inline closures or memoized factories returning a new component per render; new interval types remount every interval and defeat the body's content key.
216
+
217
+ RosterBody composes the exported RosterBodyLayout (gridZone/listZone nodes with
218
+ scroll wiring) and RosterLaneList (LegendList, geometryFor, and LaneRow mounting).
219
+ Keep its viewport gate and measured estimatedListSize in custom compositions.
220
+
221
+ ## Roster zones
222
+
223
+ | Slot | Component inputs or node | Default and behavior |
224
+ | --- | --- | --- |
225
+ | `emptyZone` | `ReactNode` | `RosterEmpty`: No lanes. |
226
+ | `laneLabelComponent` | `lane`, `flag`, `complete`, `viewTimezone`, localized labels | `RosterLaneLabel`: label, differing IANA zone badge, and notices. |
227
+ | `headerCellComponent` | `HeaderCellInput` (`tick`) | `RosterHeaderCell`: tick label. |
228
+ | `intervalComponent` | `rect`, `layer`, `lane`, `highlighted` | `RosterInterval`: positioned colored rect, with final inset bounds. |
229
+ | `gapComponent` | `rect`, `layer`, `lane` | `RosterGap`: no visible content; the row supplies pressable bounds. |
230
+ | `gridComponent` | `ticks`, `contentWidth` | `RosterGrid`: one hairline per tick behind every lane. |
231
+ | `cornerZone` | `ReactNode` | `RosterCorner`: nothing; the cell above the labels, laneLabelWidth wide. |
232
+ | `headerComponent` | `ticks`, `projection`, `scroll`, `contentWidth`, `headerCellComponent` | `RosterHeader`: frozen header following horizontal offset. |
233
+ | `laneLabelColumnComponent` | `labels: LaneLabelInput[]`, `projection`, `scroll`, `laneLabelComponent` | `RosterLaneLabelColumn`: frozen labels following vertical offset. |
234
+ | `bodyComponent` | Ordered `lanes`, `window`, `geometryFor`, `projection`, `scroll`, `press`, `ticks`, `viewport`, `contentWidth`, highlight/hover, incomplete label, and rect components | `RosterBody`: virtualized lanes. |
235
+
236
+ ## Schedule zones
237
+
238
+ | Slot | Component inputs or node | Default and behavior |
239
+ | --- | --- | --- |
240
+ | `gutterComponent` | `hours`, `pxPerHour` | `ScheduleGutter`: 24 frozen hour labels. |
241
+ | `gridComponent` | `hours`, `pxPerHour` | `ScheduleGrid`: 24 bordered hour bands behind each day's rects. |
242
+ | `dayHeaderComponent` | `day` | `ScheduleDayHeader`: weekday, localDate, and transition badge. |
243
+ | `skippedDateComponent` | `localDate` | `ScheduleSkippedDate`: zero-width header marker for a wholly skipped date. |
244
+ | `columnComponent` | `day`, `rects`, `gapRects`, `lane`, `highlightSource`, `press`, interval/gap components | `ScheduleColumn`: final rect bounds in layer order. |
245
+ | `transitionComponent` | `day`, `transition`, `y`, `dividerY`, `height`, `width` | `ScheduleTransition`: skipped-time hatch, or repeat divider and again label. |
246
+ | `nowLineComponent` | `y`, `column` | `ScheduleNowLine`: line in the current day's column, updated each minute. |
247
+ | `intervalComponent`, `gapComponent` | Same inputs as Roster | Shared `RosterInterval` and `RosterGap`. |
248
+ | `incompleteComponent` | `lane`, `label` | `ScheduleIncomplete`: notice in reserved space above the grid when incomplete. |
249
+
250
+ Position custom interval content at rect.x/y with width/height/z and
251
+ pointerEvents="none". Gap content already sits in a positioned pressable.
252
+ Column x coordinates are local; select the column through rect.column.
253
+ Slot components are plain React Native views; under NativeWind they take className
254
+ directly. The default RosterLaneLabel owns its row's bottom border, so a custom
255
+ laneLabelComponent draws its own separators.
256
+
257
+ ## Navigation and zones
258
+
259
+ windowFor(spec) returns the absolute window. anchorDate is a local YYYY-MM-DD;
260
+ changing timezone preserves it. Week starts Monday unless wkst overrides it;
261
+ Weekday is Monday=0 through Sunday=6. prev/next move one span; custom moves by
262
+ its own absolute duration. today reanchors to the view zone's local today.
263
+ Month navigation clamps to the target month's final day when needed.
264
+ Consumer controls update the controlled spec; there is no automatic toolbar.
265
+
266
+ Rule/date timezone interprets local hours during expansion. Lane timezone only
267
+ controls the IANA badge. View timezone is windowSpec.timezone, becoming
268
+ projection.viewTimezone. No other view-timezone component prop exists.
269
+
270
+ Roster uses true elapsed width (Chicago transition weeks have 167/169 hours).
271
+ Schedule always uses 24 equal wall-hour bands. Positive deltaMinutes means a
272
+ skipped empty region; negative means two half-height repeat regions. Rects split
273
+ at column edges, source changes, and every scale boundary. For repeat at T with
274
+ magnitude d, boundaries are T-d, T, and T+d. A wholly skipped date produces no
275
+ column; skippedDateComponent labels the header gap (Apia 2011-12-30).
276
+ Each column spans the first instant of its date to the first instant of the next.
277
+ Skipped wall spans belong to the date they interrupt. Its transitions include a
278
+ skip at the exclusive end when it empties that date's final wall bands (Nuuk
279
+ 2024-03-30, 23:00 to 24:00). Those bands are hatched; timeAtY returns null there,
280
+ and onCellPress does not fire.
281
+ For rollbacks across midnight (St_Johns 2009, Goose_Bay 1988), earlier-date instants
282
+ inside that span draw in a compressed repeat at the top edge, ending where
283
+ ordinary wall time resumes. timeAtY inverts this region to the absolute occurrence.
284
+ The repeat divider uses dividerY, the projected transition instant, even when
285
+ the surviving repeat regions have unequal heights.
286
+
287
+ timeAtX(projection,window,x) returns absolute time with origin from window.
288
+ timeAtY(projection,columnIndex,y) returns an absolute occurrence or null for
289
+ skipped time/outside bounds. Inverse times stay below each scale piece's exclusive
290
+ end, including immediately inside the bottom edge.
291
+ snapToStep(time,minuteStep,timezone) floors by wall clock and clamps to occurrence start if flooring would cross a transition.
292
+ minuteStep must be a positive integer divisor of 60.
293
+
294
+ ## Interaction and ordering
295
+
296
+ One press yields one callback. Hit-test final rect bounds at the original point:
297
+ intervals first, gaps second, empty space last. Highest z wins, then later layer
298
+ wins among equal z. A low-z interval therefore wins over a high-z gap. Skipped
299
+ schedule time fires nothing. Only empty-space times snap; 10:30 intervals stay
300
+ pressable on a 60-minute grid. onIntervalPress(rect,lane), onGapPress(rect,lane),
301
+ and onCellPress(lane,time) expose the outcome. Cell times clamp up to window.start
302
+ and are rejected at or beyond window.end. Roster onIntervalHover(rect,lane)
303
+ reports the winning web interval; native attaches no hover handler.
304
+
305
+ highlightSource matches a fresh {kind,id} across lanes, using highlightColor or
306
+ the normal layer color; it changes no geometry. byLabel is the default comparator.
307
+ byCoverage({measure:'availability'|'availabilityMinusBooking'}) sorts descending
308
+ with label ties. Custom LaneComparator is (a,b,coverage:ReadonlyMap<string,Coverage>)
309
+ => number. Coverage is assembled for all lanes before sorting; sorting does not
310
+ precompute geometry for offscreen lanes. Newly mounted target-cold lanes can run
311
+ layout. Scroll retains viewport offset, not a lane anchor.
312
+
313
+ ## Caches and counters
314
+
315
+ layoutLane(lane,window,projection) computes rects for visible lanes only.
316
+ coverageFor(lane,window) computes role unions for all lanes, projection-independent.
317
+ flagFor(lane,window) is cheap and uncached. Geometry assembly reads fresh flag and
318
+ coverage outside cached rects. Repeated complete inputs retain references; treat
319
+ all results as read-only.
320
+
321
+ Geometry key: lane.id + lane.version (or structural encoding of layers only) +
322
+ window bounds + every projection field. Coverage uses the same key without
323
+ projection. Bump any supplied version when layers change. Label, lane timezone,
324
+ flag, complete, and meta never invalidate rects. Clear retained windows explicitly
325
+ when a consumer no longer needs them. This cache lifetime belongs to the consumer.
326
+
327
+ | Function | Meaning |
328
+ | --- | --- |
329
+ | layoutStats() | Cumulative {runs, cacheHits}; runs are geometry computations. |
330
+ | coverageStats() | Cumulative {runs, cacheHits}; runs are coverage computations. |
331
+ | resetStats() | Zero layout and coverage counters, preserve keys. |
332
+ | clearLayoutCache(), clearCoverageCache() | Empty corresponding keys, preserve counters. |
333
+ | expandStats() | Cumulative {rules, dates, expanded, cacheHits, cacheMisses}; expanded counts occurrence computations. |
334
+ | resetExpandStats() | Zero expansion counters, preserve keys. |
335
+ | clearExpandCache() | Empty occurrence entries and retained envelopes, preserve counters. |
336
+
337
+ Target-warm means exact requested keys exist. Target-cold means they do not.
338
+ Neither describes the whole cache. A projection change can be target-cold for
339
+ layout and target-warm for coverage. Equal absolute windows reuse coverage even
340
+ when view zones differ. Reanchoring UTC to Chicago changes the absolute week and
341
+ requires coverage for all lanes, plus geometry for mounted lanes.
342
+
343
+ ## Recurrence adapter
344
+
345
+ expandRuleSet(set,window,options?) returns {intervals,gaps,envelope,complete,
346
+ truncated:[{id,droppedAtLeast}],stats}. It throws on invalid window, hours,
347
+ recurrence fields, timezone, or date input. Treat malformed upstream data as an
348
+ error; do not silently map errors to definitive empty lanes. A zero-duration window
349
+ (start=end) validates every rule and date, including date, dtstart, until, and
350
+ timezone, then returns empty intervals and gaps, complete=true, and expanded=0.
351
+
352
+ RuleSet is {rules:RosterRule[],dates:RosterDate[]}.
353
+ RosterRule requires id, kind:'include'|'exclude', frequency:'DAILY'|'WEEKLY'|'MONTHLY',
354
+ dtstart (ISO date/datetime), hourstart, hourend, and timezone (IANA).
355
+ Optional: until, count, interval, wkst, byweekday, bymonth, bymonthday, bysetpos.
356
+ RosterDate requires id, kind, date (local ISO date), and timezone; optional note
357
+ becomes Source.label. Hours are both present for partial day, both absent for
358
+ whole day. A wholly skipped local date has no occurrences for either kind, with
359
+ or without hours. Hours satisfy 0<=hourstart<hourend<=24 and may be fractional.
360
+ Unsupported fields: byyearday, byweekno, byhour, byminute, bysecond. Convert enums,
361
+ Date values, and nulls at the boundary; reject unsupported active semantics.
362
+
363
+ Expansion uses one absolute envelope for every include, exclude, and date:
364
+ envelopeFor(window) is window +/-48 absolute hours. The view zone is not an input.
365
+ Enumeration retains the original dtstart except for interval-1 rules without
366
+ COUNT whose anchor is exactly local midnight. Those rules may skip whole periods
367
+ in plain-date space, preserving the weekly weekday and monthly day, and stepping
368
+ back past nonexistent dates. Date-only and local-datetime dtstart preserve their
369
+ authored date and time, even inside a skipped date or hour. Explicit-offset dtstart
370
+ remains an instant, converted to wall fields and an offset in the rule's zone.
371
+ The engine iterates those wall fields in UTC calendar space.
372
+ Every rule enumerates from the period containing DTSTART at the anchor wall time,
373
+ aligned to WKST for WEEKLY and day 1 for MONTHLY, so interval phases follow the
374
+ DTSTART period rather than the first matching date. Dates before DTSTART are
375
+ rejected before COUNT and cap admission.
376
+ DAILY weekday filters are applied by the adapter to the authored daily sequence
377
+ because the engine otherwise re-anchors at the first matching date.
378
+ The engine uses the end of UNTIL's local date as a conservative UTC enumeration bound.
379
+ Explicit-offset UNTIL uses the end of the following local date instead; exact instant
380
+ admission is its only UNTIL admission test. Clamp either bound to the corresponding
381
+ UTC calendar bound of the envelope query.
382
+ The enumeration bound is conservative through the wall date after any cross-date rollback
383
+ at the envelope end, and envelope clipping discards the extra candidates.
384
+ The adapter owns BYSETPOS after every other BYxxx filter, grouping plain dates
385
+ by day, WKST week, or year-month before deduplication, UNTIL, COUNT, and cap admission.
386
+ Positional enumeration includes complete edge periods, then rejects dates before
387
+ DTSTART and spans outside the envelope. The engine iteration limit is the number
388
+ of authored periods from the enumeration anchor through the query bound, so empty
389
+ candidate periods terminate completely without an arbitrary cutoff. Only that
390
+ exact engine limit error signals completed enumeration; other failures propagate.
391
+ Replayed iterator passes stop before buffering positional candidates.
392
+ YEARLY is unsupported and rejected by input validation.
393
+ The adapter
394
+ drops wholly nonexistent dates before counting existing dates from the original
395
+ anchor for COUNT. A skipped date never becomes a different weekday or consumes
396
+ COUNT. The adapter admits each emitted date only when that date is at or before
397
+ the authored date-only UNTIL,
398
+ including when its final hour or the whole date is skipped. Local datetime UNTIL
399
+ compares plain date-times at the authored dtstart wall time, without normalizing
400
+ skipped hours. Explicit-offset UNTIL compares exact instants, preferring an
401
+ explicit-offset dtstart's offset in repeats and using compatible disambiguation
402
+ otherwise. A datetime UNTIL before DTSTART admits nothing.
403
+ This keeps UNTIL admission independent of anchor advancement and retained envelopes.
404
+ Occurrences outside the envelope never consume the occurrence cap.
405
+ Retained-envelope containment selects existing bounds before creating a new
406
+ padded envelope. One shared LRU spans all sets (default maxEnvelopes=4);
407
+ containment selects the most recently used matching envelope.
408
+ Per-item keys use occurrence body content + exact envelope + perRuleOccurrences.
409
+ Id and dated note attach freshly in assembly; include/exclude kind remains in the body key. Unchanged
410
+ bodies reuse occurrences only if those exact keys still exist (maxEntries=2000).
411
+ Editing a set reuses containing envelopes; unchanged item keys may still hit.
412
+ Envelope retention alone never guarantees expanded=0 after occurrence eviction.
413
+
414
+ Netting and total caps run fresh on every call, then clip to display window.
415
+ Includes supply interval sources; excludes supply gap sources. Fully excluded
416
+ includes leave gaps. No minute-step snapping. Defaults: caps.perRuleOccurrences=400,
417
+ caps.totalOccurrences=10000; options.cache.maxEntries=2000, maxEnvelopes=4.
418
+ All accept nonnegative integers. Zero cache budgets disable retention; zero caps
419
+ admit no occurrences. Both occurrence entries and retained envelopes are bounded
420
+ globally across sets; clear when discarding old windows.
421
+
422
+ Per-rule cap truncation reports droppedAtLeast=1 (unknown remainder). The total
423
+ cap admits whole lists in rule-id order, then date-id order. First list that
424
+ cannot fit and every later list are dropped; exact fully enumerated counts are
425
+ reported. Equality fits. Caps apply across the envelope, not only visible time.
426
+ Changing the total cap changes assembly, not occurrence keys; changing the
427
+ per-rule cap changes all requested occurrence keys. Incomplete output can omit
428
+ exclusions too; it is not necessarily a conservative subset. Always map
429
+ result.complete to Lane.complete.
430
+
431
+ ```ts
432
+ import type { Lane } from 'react-native-roster/core';
433
+ import { windowFor } from 'react-native-roster/core';
434
+ import { expandRuleSet } from 'react-native-roster/rrule';
435
+ import type { RuleSet } from 'react-native-roster/rrule';
436
+
437
+ export function laneFromRules(set: RuleSet): Lane {
438
+ const window = windowFor({ span: 'week', anchorDate: '2024-01-01', timezone: 'UTC' });
439
+ const result = expandRuleSet(set, window);
440
+ return { id: 'one', label: 'One', complete: result.complete, layers: [{
441
+ id: 'open', role: 'availability', z: 0, style: { color: '#4f9478' },
442
+ intervals: result.intervals, gaps: result.gaps,
443
+ }] };
444
+ }
445
+ ```
446
+
447
+ ## Gallery, evidence, and non-goals
448
+
449
+ The home page derives links from named roster and schedule fixture records.
450
+ Explicit routes cover each day/week/month at 15/30/60-minute steps, lane counts,
451
+ provenance, flags, timezones, Schedule transitions, and default/replaced zones.
452
+ Adapter weekly/override/cap routes render editable JSON beside the roster through
453
+ ruleSetEditorZone. useRuleSetDraft owns text, parse result, and the applied set;
454
+ Apply validates before updating the roster, keeping the last valid set on error.
455
+ These are demo hooks/zones, not library exports.
456
+
457
+ Every route exposes live counter functions on web window.__roster. Its identity
458
+ stays stable until unmount; on-screen snapshots sample every 500 ms. The 200-lane
459
+ route feeds real adapter output to workload W and includes cold-layout, rule-hour,
460
+ and unrelated-lane-zone controls for the browser/device protocol.
461
+
462
+ Web static export and Chromium checks complement iOS/Android implementations;
463
+ physical-device rendering and Hermes Intl behavior remain unverified here.
464
+ Production web Profiler callbacks are disabled. check:web also exports development
465
+ web and checks actual LegendList LaneRow commits on the second scroll pass, with
466
+ nonzero mount and highlight-update controls. The development 200-lane route exposes
467
+ profileStats() through window.__roster. Bun host tests remain complementary; a
468
+ native LaneRow profile is required for release. Pages deployment
469
+ is configured from main but cannot be proven by local export.
470
+
471
+ W: seed 1318, 200 lanes, week, two layers, 24-lane layout batch, 15-minute ticks;
472
+ 63 rects + 7 gap rects per lane. test/performance/baseline.json records layout
473
+ 2.070 ms and all-200-lane coverage 1.063 ms on GitHub Actions ubuntu-latest,
474
+ Bun 1.4.0, 2026-09-08T05:18:00Z, commit
475
+ 1c4ac0e8580b1d46c043f946aa7ebc1ee316d0fa. Median of 11 target-cold samples after
476
+ five runtime warmups. Each timing must stay below 16 ms; CI also enforces <=1.5x
477
+ its baseline. Size gates: core <15 kB, root <44 kB minified/uncompressed, peers
478
+ external. Phone fps and cold-layout numbers are not yet captured; the paired
479
+ react-big-scheduler comparison is not yet measured. Do not claim relative speed.
480
+
481
+ The package does not create, drag, resize, persist, or authorize intervals; it
482
+ is not a general calendar. Consumers implement their own interactions. A rule
483
+ editor in the gallery is an integration example, not a rule-authoring product.
484
+
485
+ ## Reference
486
+
487
+ - [README](./README.md): runnable quick starts, setup, zones, and measured evidence.
488
+ - [Adapter guide](./docs/adapters.md): table/feed conversion and memoization contract.
489
+ - Area landing pages: [core](./src/core/README.md), [Roster](./src/components/roster/README.md),
490
+ [Schedule](./src/components/schedule/README.md), [layers](./src/components/layers/README.md),
491
+ [primitives](./src/components/primitives/README.md), [rrule adapter](./src/adapters/rrule/README.md),
492
+ [nativewind](./src/nativewind/README.md), [gallery](./demo/components/gallery/README.md),
493
+ [showcase](./demo/components/team-roster/README.md), [theme](./demo/components/theme/README.md);
494
+ [adding an adapter](./docs/adding-an-adapter.md).
495
+ - [Design note](./docs/design.md): reasoning and non-goals.
496
+ - [Performance](./docs/performance.md): automated budgets and exact-commit device capture.
497
+ - [AGENTS.md](./AGENTS.md): repository conventions, not consumer API requirements.
498
+ - [CONTRIBUTING.md](./CONTRIBUTING.md): commands and release process.