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
@@ -0,0 +1,288 @@
1
+ import type * as RRuleModule from 'rrule-temporal' with { 'resolution-mode': 'import' };
2
+ import type * as Spec from 'temporal-spec' with { 'resolution-mode': 'import' };
3
+ import type { Weekday, Window } from '../../core';
4
+ import type { Occurrences } from './cache';
5
+ import type { RosterDate, RosterRule } from './types';
6
+
7
+ // 1.6.0 is an ES module package with a CommonJS build under its require condition
8
+ // and declarations resolved under import. Keep its actual types while selecting
9
+ // the require export for our CJS build and Metro source entry.
10
+ const { allowedWeekdays, RRuleTemporal } = require('rrule-temporal') as typeof RRuleModule;
11
+ // Both engines must use the same polyfill export; mixed import/require copies
12
+ // fail the recurrence engine's ZonedDateTime instanceof check.
13
+ const { Temporal } = require('@js-temporal/polyfill') as { Temporal: typeof Spec.Temporal };
14
+
15
+ type RuleDateTime = Spec.Temporal.PlainDateTime | Spec.Temporal.ZonedDateTime;
16
+
17
+ export function enumerate(
18
+ input: RosterRule | RosterDate,
19
+ envelope: Window,
20
+ cap: number,
21
+ // Internal reference path for testing against original-anchor enumeration.
22
+ advanceAnchor = true,
23
+ ): Occurrences {
24
+ const result: Occurrences = { spans: [], capped: false };
25
+ const admit = (date: Spec.Temporal.PlainDate): boolean => {
26
+ const span = hoursFor(date, input);
27
+ if (span.start >= envelope.end || span.end <= envelope.start || span.start >= span.end)
28
+ return true;
29
+ if (result.spans.length === cap) {
30
+ result.capped = true;
31
+ return false;
32
+ }
33
+ result.spans.push(span);
34
+ return true;
35
+ };
36
+ if ('date' in input) {
37
+ admit(Temporal.PlainDate.from(input.date));
38
+ return result;
39
+ }
40
+ const original = dateTime(input.dtstart, input.timezone);
41
+ const originalWall = plain(original);
42
+ // A cross-date rollback can leave the end on the preceding wall date.
43
+ // Include the following date completely; envelope clipping discards extra candidates.
44
+ const upper = Temporal.Instant.fromEpochMilliseconds(envelope.end)
45
+ .toZonedDateTimeISO(input.timezone)
46
+ .add({ days: 2 })
47
+ .startOfDay();
48
+ const until =
49
+ input.until === undefined
50
+ ? upper.toPlainDateTime()
51
+ : dateTime(input.until, input.timezone, true);
52
+ const untilDate =
53
+ input.until !== undefined && /^\d{4}-\d{2}-\d{2}$/.test(input.until)
54
+ ? Temporal.PlainDate.from(input.until)
55
+ : undefined;
56
+ if (
57
+ !untilDate &&
58
+ (until instanceof Temporal.ZonedDateTime
59
+ ? Temporal.ZonedDateTime.compare(
60
+ until,
61
+ anchorFor(original.toPlainDate(), original, input.timezone),
62
+ ) < 0
63
+ : Temporal.PlainDateTime.compare(until, originalWall) < 0)
64
+ )
65
+ return result;
66
+ const anchorWallTime = original.toPlainTime();
67
+ // Iterate wall dates in UTC so zone skips cannot normalize a candidate date.
68
+ // The callback applies the original date cutoff or the exact datetime UNTIL.
69
+ const untilDateEnd = (untilDate ?? until.toPlainDate())
70
+ // An instant after a midnight rollback can have the preceding local date.
71
+ // Keep the later wall date available for the callback's exact-instant filter.
72
+ .add({ days: until instanceof Temporal.ZonedDateTime ? 1 : 0 })
73
+ .toPlainDateTime('23:59:59.999')
74
+ .toZonedDateTime('UTC');
75
+ const upperDate = upper.toPlainDateTime().toZonedDateTime('UTC');
76
+ // Only midnight anchors with interval 1 and no COUNT may skip periods.
77
+ const anchor =
78
+ advanceAnchor &&
79
+ (input.interval === undefined || input.interval === 1) &&
80
+ input.count === undefined &&
81
+ anchorWallTime.equals('00:00')
82
+ ? advance(originalWall, envelope, input)
83
+ : originalWall;
84
+ const positional = !!input.bysetpos?.length;
85
+ const unit = periodUnit(input);
86
+ const firstPeriod = periodStart(anchor.toPlainDate(), input);
87
+ const bound =
88
+ Temporal.ZonedDateTime.compare(untilDateEnd, upperDate) < 0 ? untilDateEnd : upperDate;
89
+ const lastPeriod = periodStart(bound.toPlainDate(), input);
90
+ if (Temporal.PlainDate.compare(firstPeriod, lastPeriod) > 0) return result;
91
+ // 1.5.2 tests UNTIL only while visiting candidates in its monthly loop.
92
+ // Every supported loop advances at least one authored period per iteration.
93
+ // Exhaustion after the final query period is complete, even with no candidates.
94
+ const maxIterations =
95
+ Math.floor(firstPeriod.until(lastPeriod, { largestUnit: unit })[unit] / (input.interval ?? 1)) +
96
+ 1;
97
+ const engine = new RRuleTemporal({
98
+ freq: input.frequency,
99
+ dtstart: firstPeriod.toPlainDateTime(anchorWallTime).toZonedDateTime('UTC'),
100
+ // Positions must see the entire final period before UNTIL and envelope admission.
101
+ until: positional
102
+ ? lastPeriod
103
+ .add({ [unit]: 1 })
104
+ .toZonedDateTime('UTC')
105
+ .subtract({ milliseconds: 1 })
106
+ : bound,
107
+ interval: input.interval,
108
+ wkst: allowedWeekdays[input.wkst ?? 0],
109
+ byDay:
110
+ input.frequency !== 'DAILY' && input.byweekday?.length
111
+ ? input.byweekday.map((day) => allowedWeekdays[day])
112
+ : input.frequency === 'WEEKLY' && !input.bymonthday?.length
113
+ ? [allowedWeekdays[(original.dayOfWeek - 1) as Weekday]]
114
+ : undefined,
115
+ byMonth: input.bymonth,
116
+ // Explicitly preserve the implicit monthly day; 1.5.2's fallback otherwise
117
+ // constrains a 31st through February and drifts subsequent months.
118
+ byMonthDay: input.bymonthday?.length
119
+ ? input.bymonthday
120
+ : input.frequency === 'MONTHLY' && !input.byweekday?.length
121
+ ? [original.day]
122
+ : undefined,
123
+ tzid: 'UTC',
124
+ includeDtstart: false,
125
+ maxIterations,
126
+ });
127
+ // Version 1.5.2 can replay the iterator when applying its final count filter.
128
+ // Admit each local date once so replay cannot consume COUNT or the cap twice.
129
+ const visited = new Set<string>();
130
+ let admitted = 0;
131
+ const visit = (date: Spec.Temporal.PlainDate): boolean => {
132
+ if (result.capped || admitted === input.count) return false;
133
+ if (Temporal.PlainDate.compare(date, original.toPlainDate()) < 0) return true;
134
+ const key = date.toString();
135
+ if (visited.has(key)) return true;
136
+ visited.add(key);
137
+ if (untilDate) {
138
+ if (Temporal.PlainDate.compare(date, untilDate) > 0) return true;
139
+ } else if (
140
+ until instanceof Temporal.ZonedDateTime
141
+ ? Temporal.ZonedDateTime.compare(anchorFor(date, original, input.timezone), until) > 0
142
+ : Temporal.PlainDateTime.compare(date.toPlainDateTime(anchorWallTime), until) > 0
143
+ )
144
+ return true;
145
+ if (!date.toZonedDateTime(input.timezone).toPlainDate().equals(date)) return true;
146
+ // Lifetime COUNT includes existing dates before the envelope, but never skipped dates.
147
+ admitted++;
148
+ return admit(date);
149
+ };
150
+ let candidateCount = 0;
151
+ let period = '';
152
+ let candidates: Spec.Temporal.PlainDate[] = [];
153
+ const flush = (): boolean => {
154
+ const selected = candidates.filter((_, index) =>
155
+ input.bysetpos?.some((position) =>
156
+ position > 0 ? index === position - 1 : index === candidates.length + position,
157
+ ),
158
+ );
159
+ candidates = [];
160
+ return selected.every(visit);
161
+ };
162
+ try {
163
+ engine.all((occurrence, index) => {
164
+ const date = occurrence.toPlainDate();
165
+ // Replayed callback indices restart at zero. They must not enlarge a
166
+ // buffered period, especially when the whole query has only one candidate.
167
+ if (positional && index < candidateCount) return false;
168
+ candidateCount++;
169
+ // DAILY BYDAY re-anchors the engine; filter the authored sequence here.
170
+ if (
171
+ input.frequency === 'DAILY' &&
172
+ input.byweekday?.length &&
173
+ !input.byweekday.includes((date.dayOfWeek - 1) as Weekday)
174
+ )
175
+ return true;
176
+ if (!positional) return visit(date);
177
+ const key = periodStart(date, input).toString();
178
+ if (key !== period) {
179
+ if (!flush()) return false;
180
+ period = key;
181
+ }
182
+ candidates.push(date);
183
+ return true;
184
+ });
185
+ } catch (error) {
186
+ if (
187
+ !(error instanceof Error) ||
188
+ error.message !== `Maximum iterations (${maxIterations}) exceeded in all()`
189
+ )
190
+ throw error;
191
+ }
192
+ flush();
193
+ return result;
194
+ }
195
+
196
+ // Validate temporal fields even when the display window has no elapsed time.
197
+ export function validateDates(input: RosterRule | RosterDate): void {
198
+ if ('date' in input) {
199
+ Temporal.PlainDate.from(input.date).toZonedDateTime(input.timezone);
200
+ return;
201
+ }
202
+ plain(dateTime(input.dtstart, input.timezone)).toZonedDateTime(input.timezone);
203
+ if (input.until !== undefined)
204
+ plain(dateTime(input.until, input.timezone, true)).toZonedDateTime(input.timezone);
205
+ }
206
+
207
+ function hoursFor(date: Spec.Temporal.PlainDate, input: RosterRule | RosterDate): Window {
208
+ const midnight = date.toPlainDateTime();
209
+ const dayStart = date.toZonedDateTime(input.timezone);
210
+ if (!dayStart.toPlainDate().equals(date)) {
211
+ return { start: dayStart.epochMilliseconds, end: dayStart.epochMilliseconds };
212
+ }
213
+ const wholeDay = input.hourstart === undefined && input.hourend === undefined;
214
+ // Whole-day bounds resolve PlainDates to first instants. Explicit hours use
215
+ // compatible wall time, choosing the earlier repeat and advancing skips.
216
+ const at = (hour: number) => {
217
+ const wall = midnight.add({ milliseconds: Math.round(hour * 3_600_000) });
218
+ return (wholeDay ? wall.toPlainDate() : wall).toZonedDateTime(input.timezone).epochMilliseconds;
219
+ };
220
+ return { start: at(input.hourstart ?? 0), end: at(input.hourend ?? 24) };
221
+ }
222
+
223
+ function dateTime(value: string, timezone: string, endOfDate = false): RuleDateTime {
224
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
225
+ const date = Temporal.PlainDate.from(value);
226
+ return date.toPlainDateTime(endOfDate ? '23:59:59.999' : '00:00');
227
+ }
228
+ if (/(?:Z|[+-]\d{2}:?\d{2})(?:\[[^\]]*\])?$/i.test(value)) {
229
+ return Temporal.Instant.from(value).toZonedDateTimeISO(timezone);
230
+ }
231
+ return Temporal.PlainDateTime.from(value);
232
+ }
233
+
234
+ function plain(value: RuleDateTime): Spec.Temporal.PlainDateTime {
235
+ return value instanceof Temporal.ZonedDateTime ? value.toPlainDateTime() : value;
236
+ }
237
+
238
+ // Keep the original wall time and back up one period so WKST/BYDAY cannot lose
239
+ // an earlier candidate. A monthly jump must never constrain the 31st to the 28th.
240
+ function advance(
241
+ original: Spec.Temporal.PlainDateTime,
242
+ envelope: Window,
243
+ input: RosterRule,
244
+ ): Spec.Temporal.PlainDateTime {
245
+ // Include the local date crossing the left edge, regardless of anchor wall time.
246
+ const lower = Temporal.Instant.fromEpochMilliseconds(envelope.start)
247
+ .toZonedDateTimeISO(input.timezone)
248
+ .startOfDay();
249
+ const unit =
250
+ input.frequency === 'MONTHLY' ? 'months' : input.frequency === 'WEEKLY' ? 'weeks' : 'days';
251
+ const originalDate = original.toPlainDate();
252
+ const interval = input.interval ?? 1;
253
+ const distance = originalDate.until(lower.toPlainDate(), { largestUnit: unit })[unit];
254
+ let steps = Math.max(0, Math.floor(distance / interval) - 1);
255
+ while (steps > 0) {
256
+ const candidate = originalDate.add({ [unit]: steps * interval });
257
+ if (
258
+ (unit !== 'weeks' || candidate.dayOfWeek === originalDate.dayOfWeek) &&
259
+ (unit !== 'months' || candidate.day === originalDate.day) &&
260
+ candidate.toZonedDateTime(input.timezone).toPlainDate().equals(candidate)
261
+ )
262
+ return candidate.toPlainDateTime(original.toPlainTime());
263
+ steps--;
264
+ }
265
+ return original;
266
+ }
267
+
268
+ // Prefer the original offset in repeated time; otherwise resolve skips compatibly.
269
+ function anchorFor(
270
+ date: Spec.Temporal.PlainDate,
271
+ original: RuleDateTime,
272
+ timezone: string,
273
+ ): Spec.Temporal.ZonedDateTime {
274
+ return original instanceof Temporal.ZonedDateTime
275
+ ? original.with({ year: date.year, month: date.month, day: date.day }, { offset: 'prefer' })
276
+ : date.toPlainDateTime(original.toPlainTime()).toZonedDateTime(timezone);
277
+ }
278
+
279
+ function periodUnit(input: RosterRule): 'days' | 'weeks' | 'months' {
280
+ return input.frequency === 'MONTHLY' ? 'months' : input.frequency === 'WEEKLY' ? 'weeks' : 'days';
281
+ }
282
+
283
+ function periodStart(date: Spec.Temporal.PlainDate, input: RosterRule): Spec.Temporal.PlainDate {
284
+ if (input.frequency === 'MONTHLY') return date.with({ day: 1 });
285
+ if (input.frequency === 'WEEKLY')
286
+ return date.subtract({ days: (date.dayOfWeek - 1 - (input.wkst ?? 0) + 7) % 7 });
287
+ return date;
288
+ }
@@ -0,0 +1,52 @@
1
+ import type { Gap, Interval, Weekday, Window } from '../../core';
2
+
3
+ export type RosterRule = {
4
+ id: string;
5
+ kind: 'include' | 'exclude';
6
+ frequency: 'DAILY' | 'WEEKLY' | 'MONTHLY';
7
+ dtstart: string;
8
+ until?: string;
9
+ count?: number;
10
+ interval?: number;
11
+ wkst?: Weekday;
12
+ byweekday?: Weekday[];
13
+ bymonth?: number[];
14
+ bymonthday?: number[];
15
+ bysetpos?: number[];
16
+ /** Local hours in timezone, end-exclusive; 24 means next local midnight. */
17
+ hourstart: number;
18
+ hourend: number;
19
+ timezone: string;
20
+ };
21
+
22
+ export type RosterDate = {
23
+ id: string;
24
+ kind: 'include' | 'exclude';
25
+ date: string;
26
+ timezone: string;
27
+ note?: string;
28
+ /** Both hours present for a partial day, or both absent for the whole day. */
29
+ hourstart?: number;
30
+ hourend?: number;
31
+ };
32
+
33
+ export type RuleSet = { rules: RosterRule[]; dates: RosterDate[] };
34
+ export type ExpandOptions = {
35
+ cache?: { maxEntries?: number; maxEnvelopes?: number };
36
+ caps?: { perRuleOccurrences?: number; totalOccurrences?: number };
37
+ };
38
+ export type ExpandStats = {
39
+ rules: number;
40
+ dates: number;
41
+ expanded: number;
42
+ cacheHits: number;
43
+ cacheMisses: number;
44
+ };
45
+ export type ExpandResult = {
46
+ intervals: Interval[];
47
+ gaps: Gap[];
48
+ envelope: Window;
49
+ complete: boolean;
50
+ truncated: Array<{ id: string; droppedAtLeast: number }>;
51
+ stats: ExpandStats;
52
+ };
@@ -0,0 +1,78 @@
1
+ import type { Window } from '../../core';
2
+ import type { RosterDate, RosterRule } from './types';
3
+
4
+ export function validateWindow(window: Window): void {
5
+ if (
6
+ !Number.isSafeInteger(window.start) ||
7
+ !Number.isSafeInteger(window.end) ||
8
+ window.start > window.end
9
+ ) {
10
+ throw new RangeError('window must have integer epoch milliseconds with start <= end');
11
+ }
12
+ }
13
+
14
+ export function nonnegativeInteger(value: number, name: string): number {
15
+ if (!Number.isSafeInteger(value) || value < 0)
16
+ throw new RangeError(`${name} must be a nonnegative integer`);
17
+ return value;
18
+ }
19
+
20
+ export function validateInput(input: RosterRule | RosterDate): void {
21
+ if (!input.id || !['include', 'exclude'].includes(input.kind)) {
22
+ throw new RangeError('Each rule or date needs an id and an include or exclude kind');
23
+ }
24
+ // Bound date strings before any regex sees them; a full offset-and-zone
25
+ // form is under 50 characters, and unbounded input would scan quadratically.
26
+ for (const name of ['dtstart', 'until', 'date'] as const) {
27
+ const value =
28
+ name in input ? (input as Partial<Record<typeof name, unknown>>)[name] : undefined;
29
+ if (value !== undefined && (typeof value !== 'string' || value.length > 64))
30
+ throw new RangeError(`${input.id}: ${name} must be a string of at most 64 characters`);
31
+ }
32
+ const { hourstart, hourend } = input;
33
+ if ((hourstart === undefined) !== (hourend === undefined)) {
34
+ throw new RangeError(`${input.id}: hourstart and hourend must both be present or both absent`);
35
+ }
36
+ if ('frequency' in input) {
37
+ if (!['DAILY', 'WEEKLY', 'MONTHLY'].includes(input.frequency))
38
+ throw new RangeError('Unsupported frequency');
39
+ if (hourstart === undefined) throw new RangeError('Rules require hourstart and hourend');
40
+ for (const name of ['count', 'interval'] as const) {
41
+ const value = input[name];
42
+ if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0))
43
+ throw new RangeError(`${name} must be a positive integer`);
44
+ }
45
+ validateNumbers(input.wkst === undefined ? undefined : [input.wkst], 'wkst', 0, 6);
46
+ validateNumbers(input.byweekday, 'byweekday', 0, 6);
47
+ validateNumbers(input.bymonth, 'bymonth', 1, 12);
48
+ validateNumbers(input.bymonthday, 'bymonthday', -31, 31, true);
49
+ validateNumbers(input.bysetpos, 'bysetpos', -366, 366, true);
50
+ }
51
+ if (
52
+ hourstart !== undefined &&
53
+ hourend !== undefined &&
54
+ (!Number.isFinite(hourstart) ||
55
+ !Number.isFinite(hourend) ||
56
+ hourstart < 0 ||
57
+ hourstart >= hourend ||
58
+ hourend > 24)
59
+ ) {
60
+ throw new RangeError(`${input.id}: hours must satisfy 0 <= hourstart < hourend <= 24`);
61
+ }
62
+ }
63
+
64
+ function validateNumbers(
65
+ values: number[] | undefined,
66
+ name: string,
67
+ min: number,
68
+ max: number,
69
+ nonzero = false,
70
+ ): void {
71
+ if (
72
+ values?.some(
73
+ (value) => !Number.isInteger(value) || value < min || value > max || (nonzero && value === 0),
74
+ )
75
+ ) {
76
+ throw new RangeError(`Invalid ${name}`);
77
+ }
78
+ }
@@ -0,0 +1,15 @@
1
+ # layers
2
+
3
+ This area is about a layer; its children are intervals and gaps. Both
4
+ projections draw the same components, so they live above Roster and Schedule.
5
+
6
+ - Root entry exports: `RosterInterval`, `RosterGap`, `IntervalInput`, `GapInput`
7
+ - `layers.types.ts`: the component input contract both projections pass
8
+ - `parts/interval.tsx`, `parts/gap.tsx`: the default components
9
+ - `utils/styles.ts`: content-keyed style objects per layer
10
+
11
+ Imports core only. Tests: `test/components/layers`.
12
+
13
+ Both projections accept intervalComponent and gapComponent as ComponentType inputs.
14
+ Their data-owning lane or column mounts the component with the final rect, layer,
15
+ and lane; interval inputs also carry highlighted. No renderer calls a slot function.
@@ -0,0 +1,6 @@
1
+ import type { Lane, Layer, Rect } from '../../core';
2
+
3
+ /** One covered span of a layer, positioned by the projection; `highlighted` follows the active source. */
4
+ export type IntervalInput = { rect: Rect; layer: Layer; lane: Lane; highlighted: boolean };
5
+ /** One removed span of a layer, positioned by the projection. */
6
+ export type GapInput = { rect: Rect; layer: Layer; lane: Lane };
@@ -0,0 +1,5 @@
1
+ import type { GapInput } from '../layers.types';
2
+
3
+ export function RosterGap(_input: GapInput) {
4
+ return null;
5
+ }
@@ -0,0 +1,24 @@
1
+ import { View } from 'react-native';
2
+ import type { IntervalInput } from '../layers.types';
3
+ import { stylesFor } from '../utils/styles';
4
+
5
+ export function RosterInterval({ rect, layer, highlighted }: IntervalInput) {
6
+ const styles = stylesFor(layer);
7
+ const fill = highlighted ? styles.highlighted : styles.normal;
8
+ return (
9
+ <View
10
+ pointerEvents="none"
11
+ style={[
12
+ fill,
13
+ {
14
+ position: 'absolute',
15
+ left: rect.x,
16
+ top: rect.y,
17
+ width: rect.width,
18
+ height: rect.height,
19
+ zIndex: rect.z,
20
+ },
21
+ ]}
22
+ />
23
+ );
24
+ }
@@ -0,0 +1,24 @@
1
+ import type { ViewStyle } from 'react-native';
2
+ import type { Layer } from '../../../core';
3
+ import { structuralKey } from '../../../core/hash';
4
+
5
+ const styles = new Map<string, { normal: ViewStyle; highlighted: ViewStyle }>();
6
+
7
+ export function stylesFor(layer: Layer): { normal: ViewStyle; highlighted: ViewStyle } {
8
+ const key = structuralKey([layer.id, layer.style]);
9
+ let result = styles.get(key);
10
+ if (!result) {
11
+ const normal: ViewStyle = {
12
+ backgroundColor: layer.style.color,
13
+ opacity: layer.style.opacity ?? 1,
14
+ width: '100%',
15
+ height: '100%',
16
+ };
17
+ result = {
18
+ normal,
19
+ highlighted: { ...normal, backgroundColor: layer.style.highlightColor ?? layer.style.color },
20
+ };
21
+ styles.set(key, result);
22
+ }
23
+ return result;
24
+ }
@@ -0,0 +1,12 @@
1
+ # primitives
2
+
3
+ Hookless building blocks shared by Roster and Schedule. Nothing here is a
4
+ public export.
5
+
6
+ - `press-point.tsx`, `press-point.web.tsx`, `press-point.types.ts`: pointer
7
+ coordinates relative to the pressed surface; the web file is selected by the
8
+ `browser` remap in package.json
9
+ - `region-style.ts`: `regionStyle(structure, paint, override)`, which drops the
10
+ default paint when the override carries a NativeWind class entry
11
+
12
+ Tests: `test/components/primitives`.
@@ -0,0 +1,5 @@
1
+ import type { PressPoint, PressPointInput } from './press-point.types';
2
+
3
+ export function pressPoint({ nativeEvent }: PressPointInput): PressPoint {
4
+ return { x: nativeEvent.locationX as number, y: nativeEvent.locationY as number };
5
+ }
@@ -0,0 +1,5 @@
1
+ export type PressPointInput = {
2
+ nativeEvent: { locationX?: number; locationY?: number; clientX?: number; clientY?: number };
3
+ currentTarget?: unknown;
4
+ };
5
+ export type PressPoint = { x: number; y: number };
@@ -0,0 +1,19 @@
1
+ import type { PressPoint, PressPointInput } from './press-point.types';
2
+
3
+ export function pressPoint({ nativeEvent, currentTarget }: PressPointInput): PressPoint {
4
+ // React Native Web passes a DOM click, not a native responder event, to onPress.
5
+ const target = currentTarget as {
6
+ getBoundingClientRect(): { left: number; top: number; width: number; height: number };
7
+ };
8
+ const bounds = target.getBoundingClientRect();
9
+ return {
10
+ x:
11
+ typeof nativeEvent.clientX === 'number'
12
+ ? nativeEvent.clientX - bounds.left
13
+ : bounds.width / 2,
14
+ y:
15
+ typeof nativeEvent.clientY === 'number'
16
+ ? nativeEvent.clientY - bounds.top
17
+ : bounds.height / 2,
18
+ };
19
+ }
@@ -0,0 +1,21 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ /**
4
+ * Compose a chrome region's style. React Native Web renders object styles
5
+ * inline, and inline paint beats any class, so a class entry in the override
6
+ * (NativeWind's `$$css` marker) replaces the region's default paint entirely.
7
+ * Structure always applies; native style overrides simply merge last.
8
+ */
9
+ export function regionStyle(
10
+ structure: ViewStyle,
11
+ paint: ViewStyle,
12
+ override: StyleProp<ViewStyle>,
13
+ ): StyleProp<ViewStyle> {
14
+ return [structure, hasClassEntry(override) ? null : paint, override];
15
+ }
16
+
17
+ function hasClassEntry(style: unknown): boolean {
18
+ if (!style || typeof style !== 'object') return false;
19
+ if (Array.isArray(style)) return style.some((entry) => hasClassEntry(entry));
20
+ return '$$css' in style;
21
+ }
@@ -0,0 +1,29 @@
1
+ # Roster
2
+
3
+ This feature is about a roster; its children are lanes.
4
+
5
+ - Root entry exports: `Roster`, `useRoster`, `RosterBodyLayout`, `RosterLaneList`, the other `RosterXxx` parts, and every
6
+ `XxxInput` slot input type from `roster.types.ts`
7
+
8
+ - `index.tsx`: the chassis; calls `useRoster`, branches on `status`, composes zones
9
+ - `use-roster.ts`: the hook; owns window, projection, scroll, geometry, and press
10
+ - `layout.tsx`: arranges corner, header, label column, and body regions only
11
+ - `roster.types.ts`: props, model, and every slot input type
12
+ - `body-layout.tsx`: arranges grid and list nodes with horizontal scroll wiring
13
+ - `parts/lane-list.tsx`: `RosterLaneList` owns LegendList and its lane render callback
14
+ - `parts/`: collection-level parts (body, header, header cell, grid, corner, label column, empty)
15
+ - `lanes/`: `LaneRow`, the interval-hover platform pair, and lane-local parts
16
+ - `utils/`: ticks and the body content key
17
+
18
+ Interval and gap components live in `../layers`; press geometry and `regionStyle`
19
+ in `../primitives`. Zone contracts are documented in the README's Roster zones
20
+ section and in `llms.txt`. Tests: `test/components/roster`.
21
+
22
+ This feature is about a roster body; its children are lanes. `RosterBody` gates
23
+ measurement and composes the body layout and lane list. The `onRowRender` prop on
24
+ `RosterBody` and `RosterLaneList` is a development-only Profiler hook used by the
25
+ gallery, not a supported customization point. Production Profiler callbacks are disabled.
26
+
27
+ Input-bearing slots accept component types and mount in the data-owning parts.
28
+ The chassis binds defaults once; emptyZone and cornerZone accept nodes. The body
29
+ content key tracks interval and gap component identity, including class components.
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from 'react';
2
+ import { ScrollView, View } from 'react-native';
3
+ import type { BodyInput } from './roster.types';
4
+
5
+ export function RosterBodyLayout({
6
+ scroll,
7
+ contentWidth,
8
+ viewport,
9
+ gridZone,
10
+ listZone,
11
+ }: Pick<BodyInput, 'scroll' | 'contentWidth' | 'viewport'> & {
12
+ /** Noninteractive grid behind the lane list. */
13
+ gridZone: ReactNode;
14
+ /** Virtualized lane collection within the measured viewport. */
15
+ listZone: ReactNode;
16
+ }) {
17
+ return (
18
+ <ScrollView
19
+ testID="roster-horizontal-scroll"
20
+ ref={scroll.bodyRef}
21
+ horizontal
22
+ onScroll={scroll.onBodyScroll}
23
+ scrollEventThrottle={16}
24
+ style={{ flex: 1 }}
25
+ >
26
+ <View style={{ width: contentWidth, height: viewport.height }}>
27
+ {gridZone}
28
+ {listZone}
29
+ </View>
30
+ </ScrollView>
31
+ );
32
+ }