react-native-roster 0.0.1 → 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 (81) hide show
  1. package/README.md +108 -31
  2. package/dist/src/components/roster/body-layout.d.ts +9 -0
  3. package/dist/src/components/roster/body-layout.d.ts.map +1 -0
  4. package/dist/src/components/roster/body-layout.js +9 -0
  5. package/dist/src/components/roster/body-layout.js.map +1 -0
  6. package/dist/src/components/roster/index.d.ts +1 -1
  7. package/dist/src/components/roster/index.d.ts.map +1 -1
  8. package/dist/src/components/roster/index.js +10 -26
  9. package/dist/src/components/roster/index.js.map +1 -1
  10. package/dist/src/components/roster/lanes/lane.d.ts +2 -2
  11. package/dist/src/components/roster/lanes/lane.d.ts.map +1 -1
  12. package/dist/src/components/roster/lanes/lane.js +3 -8
  13. package/dist/src/components/roster/lanes/lane.js.map +1 -1
  14. package/dist/src/components/roster/parts/body.d.ts +3 -3
  15. package/dist/src/components/roster/parts/body.d.ts.map +1 -1
  16. package/dist/src/components/roster/parts/body.js +5 -20
  17. package/dist/src/components/roster/parts/body.js.map +1 -1
  18. package/dist/src/components/roster/parts/header-cell.d.ts +2 -4
  19. package/dist/src/components/roster/parts/header-cell.d.ts.map +1 -1
  20. package/dist/src/components/roster/parts/header-cell.js.map +1 -1
  21. package/dist/src/components/roster/parts/header.d.ts +1 -1
  22. package/dist/src/components/roster/parts/header.d.ts.map +1 -1
  23. package/dist/src/components/roster/parts/header.js +2 -2
  24. package/dist/src/components/roster/parts/header.js.map +1 -1
  25. package/dist/src/components/roster/parts/lane-label-column.d.ts +1 -1
  26. package/dist/src/components/roster/parts/lane-label-column.d.ts.map +1 -1
  27. package/dist/src/components/roster/parts/lane-label-column.js +2 -2
  28. package/dist/src/components/roster/parts/lane-label-column.js.map +1 -1
  29. package/dist/src/components/roster/parts/lane-list.d.ts +7 -0
  30. package/dist/src/components/roster/parts/lane-list.d.ts.map +1 -0
  31. package/dist/src/components/roster/parts/lane-list.js +25 -0
  32. package/dist/src/components/roster/parts/lane-list.js.map +1 -0
  33. package/dist/src/components/roster/roster.types.d.ts +19 -20
  34. package/dist/src/components/roster/roster.types.d.ts.map +1 -1
  35. package/dist/src/components/roster/utils/body-content-key.d.ts +1 -1
  36. package/dist/src/components/roster/utils/body-content-key.d.ts.map +1 -1
  37. package/dist/src/components/roster/utils/body-content-key.js +10 -10
  38. package/dist/src/components/roster/utils/body-content-key.js.map +1 -1
  39. package/dist/src/components/schedule/days/day.d.ts +2 -2
  40. package/dist/src/components/schedule/days/day.d.ts.map +1 -1
  41. package/dist/src/components/schedule/days/day.js +2 -11
  42. package/dist/src/components/schedule/days/day.js.map +1 -1
  43. package/dist/src/components/schedule/days/header-layout.d.ts +1 -1
  44. package/dist/src/components/schedule/days/header-layout.d.ts.map +1 -1
  45. package/dist/src/components/schedule/days/parts/column.d.ts +1 -1
  46. package/dist/src/components/schedule/days/parts/column.d.ts.map +1 -1
  47. package/dist/src/components/schedule/days/parts/column.js +3 -8
  48. package/dist/src/components/schedule/days/parts/column.js.map +1 -1
  49. package/dist/src/components/schedule/index.d.ts.map +1 -1
  50. package/dist/src/components/schedule/index.js +7 -8
  51. package/dist/src/components/schedule/index.js.map +1 -1
  52. package/dist/src/components/schedule/schedule.types.d.ts +17 -17
  53. package/dist/src/components/schedule/schedule.types.d.ts.map +1 -1
  54. package/dist/src/index.d.ts +3 -1
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +5 -1
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/nativewind/index.d.ts +24 -26
  59. package/dist/src/nativewind/index.d.ts.map +1 -1
  60. package/docs/migrations.md +61 -0
  61. package/llms.txt +46 -29
  62. package/package.json +3 -2
  63. package/src/components/layers/README.md +7 -3
  64. package/src/components/roster/README.md +15 -4
  65. package/src/components/roster/body-layout.tsx +32 -0
  66. package/src/components/roster/index.tsx +63 -45
  67. package/src/components/roster/lanes/lane.tsx +16 -11
  68. package/src/components/roster/parts/body.tsx +17 -84
  69. package/src/components/roster/parts/header-cell.tsx +2 -2
  70. package/src/components/roster/parts/header.tsx +7 -2
  71. package/src/components/roster/parts/lane-label-column.tsx +2 -2
  72. package/src/components/roster/parts/lane-list.tsx +74 -0
  73. package/src/components/roster/roster.types.ts +17 -16
  74. package/src/components/roster/utils/body-content-key.ts +13 -13
  75. package/src/components/schedule/README.md +8 -3
  76. package/src/components/schedule/days/day.tsx +31 -18
  77. package/src/components/schedule/days/header-layout.tsx +1 -1
  78. package/src/components/schedule/days/parts/column.tsx +10 -10
  79. package/src/components/schedule/index.tsx +20 -21
  80. package/src/components/schedule/schedule.types.ts +13 -13
  81. package/src/index.ts +3 -0
@@ -11,18 +11,16 @@ export declare const scheduleClassNames: Mapping<ScheduleStyleProps>;
11
11
  export declare const Roster: import("react").ComponentType<import("../components/roster/roster.types").RosterInput & RosterStyleProps & {
12
12
  incompleteLabel?: string;
13
13
  neverSetLabel?: string;
14
- laneLabelZone?: (input: import("../components/roster/roster.types").LaneLabelInput) => import("react").ReactNode;
15
- headerCellZone?: (input: {
16
- tick: import("../components/roster/roster.types").RosterTick;
17
- }) => import("react").ReactNode;
18
- intervalZone?: (input: import("..").IntervalInput) => import("react").ReactNode;
19
- gapZone?: (input: import("..").GapInput) => import("react").ReactNode;
20
- gridZone?: (input: import("../components/roster/roster.types").GridInput) => import("react").ReactNode;
21
- emptyZone?: () => import("react").ReactNode;
22
- cornerZone?: () => import("react").ReactNode;
23
- headerZone?: (input: import("../components/roster/roster.types").HeaderInput) => import("react").ReactNode;
24
- laneLabelColumnZone?: (input: import("../components/roster/roster.types").LabelColumnInput) => import("react").ReactNode;
25
- bodyZone?: (input: import("../components/roster/roster.types").BodyInput) => import("react").ReactNode;
14
+ laneLabelComponent?: import("react").ComponentType<import("../components/roster/roster.types").LaneLabelInput>;
15
+ headerCellComponent?: import("react").ComponentType<import("../components/roster/roster.types").HeaderCellInput>;
16
+ intervalComponent?: import("react").ComponentType<import("..").IntervalInput>;
17
+ gapComponent?: import("react").ComponentType<import("..").GapInput>;
18
+ gridComponent?: import("react").ComponentType<import("../components/roster/roster.types").GridInput>;
19
+ emptyZone?: import("react").ReactNode;
20
+ cornerZone?: import("react").ReactNode;
21
+ headerComponent?: import("react").ComponentType<import("../components/roster/roster.types").HeaderInput>;
22
+ laneLabelColumnComponent?: import("react").ComponentType<import("../components/roster/roster.types").LabelColumnInput>;
23
+ bodyComponent?: import("react").ComponentType<import("../components/roster/roster.types").BodyInput>;
26
24
  } & {
27
25
  bodyClassName?: string | undefined;
28
26
  className?: string | undefined;
@@ -31,26 +29,26 @@ export declare const Roster: import("react").ComponentType<import("../components
31
29
  }>;
32
30
  export declare const Schedule: import("react").ComponentType<import("../components/schedule/schedule.types").ScheduleInput & ScheduleStyleProps & {
33
31
  incompleteLabel?: string;
34
- gutterZone?: (input: import("../components/schedule/schedule.types").ScheduleHoursInput) => import("react").ReactNode;
35
- gridZone?: (input: import("../components/schedule/schedule.types").ScheduleHoursInput) => import("react").ReactNode;
36
- dayHeaderZone?: (input: {
32
+ gutterComponent?: import("react").ComponentType<import("../components/schedule/schedule.types").ScheduleHoursInput>;
33
+ gridComponent?: import("react").ComponentType<import("../components/schedule/schedule.types").ScheduleHoursInput>;
34
+ dayHeaderComponent?: import("react").ComponentType<{
37
35
  day: import("..").DayColumn;
38
- }) => import("react").ReactNode;
39
- skippedDateZone?: (input: {
36
+ }>;
37
+ skippedDateComponent?: import("react").ComponentType<{
40
38
  localDate: string;
41
- }) => import("react").ReactNode;
42
- columnZone?: (input: import("../components/schedule/schedule.types").ScheduleColumnInput) => import("react").ReactNode;
43
- transitionZone?: (input: import("../components/schedule/schedule.types").ScheduleTransitionInput) => import("react").ReactNode;
44
- nowLineZone?: (input: {
39
+ }>;
40
+ columnComponent?: import("react").ComponentType<import("../components/schedule/schedule.types").ScheduleColumnInput>;
41
+ transitionComponent?: import("react").ComponentType<import("../components/schedule/schedule.types").ScheduleTransitionInput>;
42
+ nowLineComponent?: import("react").ComponentType<{
45
43
  y: number;
46
44
  column: number;
47
- }) => import("react").ReactNode;
48
- intervalZone?: (input: import("..").IntervalInput) => import("react").ReactNode;
49
- gapZone?: (input: import("..").GapInput) => import("react").ReactNode;
50
- incompleteZone?: (input: {
45
+ }>;
46
+ intervalComponent?: import("react").ComponentType<import("..").IntervalInput>;
47
+ gapComponent?: import("react").ComponentType<import("..").GapInput>;
48
+ incompleteComponent?: import("react").ComponentType<{
51
49
  lane: import("..").Lane;
52
50
  label: string;
53
- }) => import("react").ReactNode;
51
+ }>;
54
52
  } & {
55
53
  className?: string | undefined;
56
54
  daysClassName?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/nativewind/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,KAAK,aAAa,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,WAAW,GAAG,GAAG,MAAM,WAAW,CAAC,CAAC;AACtF,KAAK,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,MAAM,WAAW,GACrE,GAAG,MAAM,OAAO,GAChB,OAAO,CAAC;AACZ,KAAK,OAAO,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;CAAE,CAAC;AAEhE,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,CAKtD,CAAC;AACF,eAAO,MAAM,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAK1D,CAAC;AAIF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;EAA2C,CAAC;AAC/D,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA+C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/nativewind/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,KAAK,aAAa,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,WAAW,GAAG,GAAG,MAAM,WAAW,CAAC,CAAC;AACtF,KAAK,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,MAAM,WAAW,GACrE,GAAG,MAAM,OAAO,GAChB,OAAO,CAAC;AACZ,KAAK,OAAO,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;CAAE,CAAC;AAEhE,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,CAKtD,CAAC;AACF,eAAO,MAAM,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,CAK1D,CAAC;AAIF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;EAA2C,CAAC;AAC/D,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA+C,CAAC"}
@@ -0,0 +1,61 @@
1
+ # Migrations
2
+
3
+ Changes to public props that need consumer edits, by the version that ships them.
4
+ The changelog lists every change; this page carries only the ones with a
5
+ migration step.
6
+
7
+ ## 0.1.0: slot components replace render functions
8
+
9
+ Every Roster and Schedule prop that used to take a render function now takes a
10
+ component type or a node. Props ending in `Component` accept
11
+ `ComponentType<Input>` and are mounted by React with the same input as props.
12
+ Props ending in `Zone` accept `ReactNode`. Press callbacks (`onIntervalPress`,
13
+ `onGapPress`, `onCellPress`, `onIntervalHover`, `onNavigate`) are unchanged.
14
+
15
+ | Before (0.0.x) | After (0.1.0) | Input |
16
+ | --- | --- | --- |
17
+ | `emptyZone={() => <Empty />}` | `emptyZone={<Empty />}` | none |
18
+ | `cornerZone={() => <Corner />}` | `cornerZone={<Corner />}` | none |
19
+ | `headerZone={(input) => ...}` | `headerComponent={Header}` | `HeaderInput` |
20
+ | `laneLabelColumnZone={(input) => ...}` | `laneLabelColumnComponent={LabelColumn}` | `LabelColumnInput` |
21
+ | `bodyZone={(input) => ...}` | `bodyComponent={Body}` | `BodyInput` |
22
+ | `headerCellZone={({ tick }) => ...}` | `headerCellComponent={HeaderCell}` | `HeaderCellInput` |
23
+ | `laneLabelZone={(input) => ...}` | `laneLabelComponent={LaneLabel}` | `LaneLabelInput` |
24
+ | `intervalZone={(input) => ...}` | `intervalComponent={Interval}` | `IntervalInput` |
25
+ | `gapZone={(input) => ...}` | `gapComponent={Gap}` | `GapInput` |
26
+ | `gridZone={(input) => ...}` | `gridComponent={Grid}` | `GridInput` |
27
+
28
+ Schedule follows the same rule: `gutterZone`, `gridZone`, `dayHeaderZone`,
29
+ `skippedDateZone`, `columnZone`, `transitionZone`, `nowLineZone`, `intervalZone`,
30
+ `gapZone`, and `incompleteZone` become the matching `Component` props with the
31
+ same inputs.
32
+
33
+ Nested contracts renamed with them: `HeaderInput.headerCellZone` is now
34
+ `headerCellComponent`, `LabelColumnInput.laneLabelZone` is now
35
+ `laneLabelComponent`, and `BodyInput` carries `intervalComponent`,
36
+ `gapComponent`, and `gridComponent`.
37
+
38
+ Mechanical rewrite for a render function that was already a plain function of
39
+ its input:
40
+
41
+ ```tsx
42
+ // Before
43
+ <Roster intervalZone={({ rect, layer }) => <Bar rect={rect} layer={layer} />} />
44
+
45
+ // After: the same function, declared once at module scope and passed as a type
46
+ function Interval({ rect, layer }: IntervalInput) {
47
+ return <Bar rect={rect} layer={layer} />;
48
+ }
49
+ <Roster intervalComponent={Interval} />
50
+ ```
51
+
52
+ Declare slot components at module scope. An inline arrow creates a new
53
+ component type every render, which remounts every interval and defeats the
54
+ body's content key.
55
+
56
+ The default parts are exported under their existing names (`RosterInterval`,
57
+ `RosterGap`, `RosterLaneLabel`, `RosterHeaderCell`, `RosterGrid`, `RosterHeader`,
58
+ `RosterLaneLabelColumn`, `RosterBody`, `RosterEmpty`, `RosterCorner`) and can be
59
+ composed inside a replacement. `RosterBody` is now built from `RosterBodyLayout`
60
+ (arranges `gridZone` and `listZone`) and `RosterLaneList` (owns the virtualized
61
+ lane list); both are exported for consumers that replace the body.
package/llms.txt CHANGED
@@ -144,12 +144,12 @@ show the incompleteLabel; do not describe incomplete empty time as definitive.
144
144
 
145
145
  | Import | Runtime exports and purpose |
146
146
  | --- | --- |
147
- | react-native-roster | Roster, Schedule, useRoster, useSchedule, default zone fillers, and core re-exports. |
147
+ | react-native-roster | Roster, Schedule, useRoster, useSchedule, default slot components, and core re-exports. |
148
148
  | react-native-roster/core | layoutLane, coverageFor, flagFor, windowFor, prev, next, today, dayColumnsFor, timeAtX, timeAtY, snapToStep, byLabel, byCoverage, and core counters/caches. |
149
149
  | react-native-roster/rrule | expandRuleSet, envelopeFor, expandStats, resetExpandStats, clearExpandCache; RuleSet, RosterRule, RosterDate, ExpandOptions, ExpandResult, and ExpandStats types. |
150
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
151
 
152
- No default exports. Root exports renderer inputs/models/zone types as well as
152
+ No default exports. Root exports renderer inputs/models/slot input types as well as
153
153
  core types. Core imports standard JS and Intl only. Only /rrule imports
154
154
  rrule-temporal 1.6.0 and @js-temporal/polyfill 0.5.1. Those are installed runtime
155
155
  dependencies but cannot enter root/core bundles through library imports.
@@ -160,6 +160,14 @@ from dist/src; declarations use the types condition. Build before Node export
160
160
  tests. Root needs a native renderer or web bundler; direct Node use of root is
161
161
  not a substitute for a React Native app. Core and /rrule work in Node/Bun.
162
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
+
163
171
  ## Renderer contracts
164
172
 
165
173
  Roster required props: lanes: Lane[], windowSpec: WindowSpec.
@@ -198,44 +206,53 @@ The chassis measures width, reserves a 48 px gutter, and fits all actual day
198
206
  columns without horizontal scrolling; the body scrolls vertically. now updates
199
207
  every minute and is null outside the selected window.
200
208
 
201
- Zone props are optional render functions. Use exported fillers inside replacements
202
- to preserve geometry, scrolling, and interaction. Types are exported from root.
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.
203
220
 
204
221
  ## Roster zones
205
222
 
206
- | Zone | Receives | Default filler and behavior |
223
+ | Slot | Component inputs or node | Default and behavior |
207
224
  | --- | --- | --- |
208
- | `emptyZone` | Nothing | `RosterEmpty`: No lanes. |
209
- | `laneLabelZone` | `lane`, `flag`, `complete`, `viewTimezone`, localized labels | `RosterLaneLabel`: label, differing IANA zone badge, and notices. |
210
- | `headerCellZone` | `tick` | `RosterHeaderCell`: tick label. |
211
- | `intervalZone` | `rect`, `layer`, `lane`, `highlighted` | `RosterInterval`: positioned colored rect, with final inset bounds. |
212
- | `gapZone` | `rect`, `layer`, `lane` | `RosterGap`: no visible content; the row supplies pressable bounds. |
213
- | `gridZone` | `ticks`, `contentWidth` | `RosterGrid`: one hairline per tick behind every lane. |
214
- | `cornerZone` | Nothing | `RosterCorner`: nothing; the cell above the labels, laneLabelWidth wide. |
215
- | `headerZone` | `ticks`, `projection`, `scroll`, `contentWidth`, `headerCellZone` | `RosterHeader`: frozen header following horizontal offset. |
216
- | `laneLabelColumnZone` | `labels: LaneLabelInput[]`, `projection`, `scroll`, `laneLabelZone` | `RosterLaneLabelColumn`: frozen labels following vertical offset. |
217
- | `bodyZone` | Ordered `lanes`, `window`, `geometryFor`, `projection`, `scroll`, `press`, `ticks`, `viewport`, `contentWidth`, highlight/hover, incomplete label, and rect zones | `RosterBody`: virtualized lanes. |
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. |
218
235
 
219
236
  ## Schedule zones
220
237
 
221
- | Zone | Receives | Default filler and behavior |
238
+ | Slot | Component inputs or node | Default and behavior |
222
239
  | --- | --- | --- |
223
- | `gutterZone` | `hours`, `pxPerHour` | `ScheduleGutter`: 24 frozen hour labels. |
224
- | `gridZone` | `hours`, `pxPerHour` | `ScheduleGrid`: 24 bordered hour bands behind each day's rects. |
225
- | `dayHeaderZone` | `day` | `ScheduleDayHeader`: weekday, localDate, and transition badge. |
226
- | `skippedDateZone` | `localDate` | `ScheduleSkippedDate`: zero-width header marker for a wholly skipped date. |
227
- | `columnZone` | `day`, `rects`, `gapRects`, `lane`, `highlightSource`, `press`, interval/gap zones | `ScheduleColumn`: final rect bounds in layer order. |
228
- | `transitionZone` | `day`, `transition`, `y`, `dividerY`, `height`, `width` | `ScheduleTransition`: skipped-time hatch, or repeat divider and again label. |
229
- | `nowLineZone` | `y`, `column` | `ScheduleNowLine`: line in the current day's column, updated each minute. |
230
- | `intervalZone`, `gapZone` | Same inputs as Roster | Shared `RosterInterval` and `RosterGap`. |
231
- | `incompleteZone` | `lane`, `label` | `ScheduleIncomplete`: notice in reserved space above the grid when incomplete. |
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. |
232
249
 
233
250
  Position custom interval content at rect.x/y with width/height/z and
234
251
  pointerEvents="none". Gap content already sits in a positioned pressable.
235
252
  Column x coordinates are local; select the column through rect.column.
236
- Zone fillers are plain React Native views; under NativeWind they take className
253
+ Slot components are plain React Native views; under NativeWind they take className
237
254
  directly. The default RosterLaneLabel owns its row's bottom border, so a custom
238
- laneLabelZone draws its own separators.
255
+ laneLabelComponent draws its own separators.
239
256
 
240
257
  ## Navigation and zones
241
258
 
@@ -255,7 +272,7 @@ Schedule always uses 24 equal wall-hour bands. Positive deltaMinutes means a
255
272
  skipped empty region; negative means two half-height repeat regions. Rects split
256
273
  at column edges, source changes, and every scale boundary. For repeat at T with
257
274
  magnitude d, boundaries are T-d, T, and T+d. A wholly skipped date produces no
258
- column; skippedDateZone labels the header gap (Apia 2011-12-30).
275
+ column; skippedDateComponent labels the header gap (Apia 2011-12-30).
259
276
  Each column spans the first instant of its date to the first instant of the next.
260
277
  Skipped wall spans belong to the date they interrupt. Its transitions include a
261
278
  skip at the exclusive end when it empties that date's final wall bands (Nuuk
@@ -457,7 +474,7 @@ W: seed 1318, 200 lanes, week, two layers, 24-lane layout batch, 15-minute ticks
457
474
  Bun 1.4.0, 2026-09-08T05:18:00Z, commit
458
475
  1c4ac0e8580b1d46c043f946aa7ebc1ee316d0fa. Median of 11 target-cold samples after
459
476
  five runtime warmups. Each timing must stay below 16 ms; CI also enforces <=1.5x
460
- its baseline. Size gates: core <15 kB, root <40 kB minified/uncompressed, peers
477
+ its baseline. Size gates: core <15 kB, root <44 kB minified/uncompressed, peers
461
478
  external. Phone fps and cold-layout numbers are not yet captured; the paired
462
479
  react-big-scheduler comparison is not yet measured. Do not claim relative speed.
463
480
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-roster",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Layered intervals with provenance across lanes and a shared time axis for React Native.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -66,7 +66,8 @@
66
66
  "README.md",
67
67
  "LICENSE",
68
68
  "NOTICE.md",
69
- "llms.txt"
69
+ "llms.txt",
70
+ "docs/migrations.md"
70
71
  ],
71
72
  "scripts": {
72
73
  "clean": "rm -rf dist",
@@ -1,11 +1,15 @@
1
1
  # layers
2
2
 
3
3
  This area is about a layer; its children are intervals and gaps. Both
4
- projections draw the same fillers, so they live above Roster and Schedule.
4
+ projections draw the same components, so they live above Roster and Schedule.
5
5
 
6
6
  - Root entry exports: `RosterInterval`, `RosterGap`, `IntervalInput`, `GapInput`
7
- - `layers.types.ts`: the filler input contract both projections pass
8
- - `parts/interval.tsx`, `parts/gap.tsx`: the default fillers
7
+ - `layers.types.ts`: the component input contract both projections pass
8
+ - `parts/interval.tsx`, `parts/gap.tsx`: the default components
9
9
  - `utils/styles.ts`: content-keyed style objects per layer
10
10
 
11
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.
@@ -2,17 +2,28 @@
2
2
 
3
3
  This feature is about a roster; its children are lanes.
4
4
 
5
- - Root entry exports: `Roster`, `useRoster`, the `RosterXxx` parts, and every
6
- `XxxInput` zone type from `roster.types.ts`
5
+ - Root entry exports: `Roster`, `useRoster`, `RosterBodyLayout`, `RosterLaneList`, the other `RosterXxx` parts, and every
6
+ `XxxInput` slot input type from `roster.types.ts`
7
7
 
8
8
  - `index.tsx`: the chassis; calls `useRoster`, branches on `status`, composes zones
9
9
  - `use-roster.ts`: the hook; owns window, projection, scroll, geometry, and press
10
10
  - `layout.tsx`: arranges corner, header, label column, and body regions only
11
- - `roster.types.ts`: props, model, and every zone input type
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
12
14
  - `parts/`: collection-level parts (body, header, header cell, grid, corner, label column, empty)
13
15
  - `lanes/`: `LaneRow`, the interval-hover platform pair, and lane-local parts
14
16
  - `utils/`: ticks and the body content key
15
17
 
16
- Interval and gap fillers live in `../layers`; press geometry and `regionStyle`
18
+ Interval and gap components live in `../layers`; press geometry and `regionStyle`
17
19
  in `../primitives`. Zone contracts are documented in the README's Roster zones
18
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
+ }
@@ -12,6 +12,9 @@ import { RosterLaneLabelColumn } from './parts/lane-label-column';
12
12
  import type { RosterProps } from './roster.types';
13
13
  import { useRoster } from './use-roster';
14
14
 
15
+ const defaultEmptyZone = <RosterEmpty />;
16
+ const defaultCornerZone = <RosterCorner />;
17
+
15
18
  export function Roster(props: RosterProps) {
16
19
  const {
17
20
  status,
@@ -28,20 +31,20 @@ export function Roster(props: RosterProps) {
28
31
  onLayout,
29
32
  } = useRoster(props);
30
33
  const {
31
- emptyZone = RosterEmpty,
32
- cornerZone = RosterCorner,
33
- headerZone = RosterHeader,
34
- laneLabelColumnZone = RosterLaneLabelColumn,
35
- bodyZone = RosterBody,
36
- headerCellZone = RosterHeaderCell,
37
- laneLabelZone = RosterLaneLabel,
38
- intervalZone = RosterInterval,
39
- gapZone = RosterGap,
40
- gridZone = RosterGrid,
34
+ emptyZone = defaultEmptyZone,
35
+ cornerZone = defaultCornerZone,
36
+ headerComponent: HeaderComponent = RosterHeader,
37
+ laneLabelColumnComponent: LaneLabelColumnComponent = RosterLaneLabelColumn,
38
+ bodyComponent: BodyComponent = RosterBody,
39
+ headerCellComponent = RosterHeaderCell,
40
+ laneLabelComponent = RosterLaneLabel,
41
+ intervalComponent = RosterInterval,
42
+ gapComponent = RosterGap,
43
+ gridComponent = RosterGrid,
41
44
  incompleteLabel = 'Availability may be incomplete',
42
45
  neverSetLabel = 'No availability set',
43
46
  } = props;
44
- if (status === 'empty') return emptyZone();
47
+ if (status === 'empty') return emptyZone;
45
48
  if (window.start === window.end) return null;
46
49
  return (
47
50
  <RosterLayout
@@ -51,40 +54,55 @@ export function Roster(props: RosterProps) {
51
54
  bodyStyle={props.bodyStyle}
52
55
  laneLabelWidth={props.laneLabelWidth}
53
56
  onLayout={onLayout}
54
- cornerZone={cornerZone()}
55
- headerZone={headerZone({ ticks, projection, scroll, contentWidth, headerCellZone })}
56
- laneLabelColumnZone={laneLabelColumnZone({
57
- labels: orderedLanes.map((lane) => {
58
- const state = laneState.get(lane.id) ?? { flag: 'none', complete: true };
59
- return {
60
- lane,
61
- ...state,
62
- viewTimezone: projection.viewTimezone,
63
- incompleteLabel,
64
- neverSetLabel,
65
- };
66
- }),
67
- projection,
68
- scroll,
69
- laneLabelZone,
70
- })}
71
- bodyZone={bodyZone({
72
- lanes: orderedLanes,
73
- window,
74
- geometryFor,
75
- projection,
76
- scroll,
77
- press,
78
- ticks,
79
- contentWidth,
80
- viewport,
81
- intervalZone,
82
- gapZone,
83
- gridZone,
84
- highlightSource: props.highlightSource,
85
- onIntervalHover: props.onIntervalHover,
86
- incompleteLabel,
87
- })}
57
+ cornerZone={cornerZone}
58
+ headerZone={
59
+ <HeaderComponent
60
+ ticks={ticks}
61
+ projection={projection}
62
+ scroll={scroll}
63
+ contentWidth={contentWidth}
64
+ headerCellComponent={headerCellComponent}
65
+ />
66
+ }
67
+ laneLabelColumnZone={
68
+ <LaneLabelColumnComponent
69
+ labels={orderedLanes.map((lane) => {
70
+ const state = laneState.get(lane.id) ?? {
71
+ flag: 'none',
72
+ complete: true,
73
+ };
74
+ return {
75
+ lane,
76
+ ...state,
77
+ viewTimezone: projection.viewTimezone,
78
+ incompleteLabel,
79
+ neverSetLabel,
80
+ };
81
+ })}
82
+ projection={projection}
83
+ scroll={scroll}
84
+ laneLabelComponent={laneLabelComponent}
85
+ />
86
+ }
87
+ bodyZone={
88
+ <BodyComponent
89
+ lanes={orderedLanes}
90
+ window={window}
91
+ geometryFor={geometryFor}
92
+ projection={projection}
93
+ scroll={scroll}
94
+ press={press}
95
+ ticks={ticks}
96
+ contentWidth={contentWidth}
97
+ viewport={viewport}
98
+ intervalComponent={intervalComponent}
99
+ gapComponent={gapComponent}
100
+ gridComponent={gridComponent}
101
+ highlightSource={props.highlightSource}
102
+ onIntervalHover={props.onIntervalHover}
103
+ incompleteLabel={incompleteLabel}
104
+ />
105
+ }
88
106
  />
89
107
  );
90
108
  }
@@ -8,7 +8,12 @@ import { RosterIncomplete } from './parts/incomplete';
8
8
 
9
9
  export type LaneRowProps = Pick<
10
10
  BodyInput,
11
- 'press' | 'intervalZone' | 'gapZone' | 'highlightSource' | 'onIntervalHover' | 'incompleteLabel'
11
+ | 'press'
12
+ | 'intervalComponent'
13
+ | 'gapComponent'
14
+ | 'highlightSource'
15
+ | 'onIntervalHover'
16
+ | 'incompleteLabel'
12
17
  > & {
13
18
  lane: Lane;
14
19
  geometry: LaneGeometry;
@@ -22,8 +27,8 @@ export function LaneRow({
22
27
  width,
23
28
  rowHeight,
24
29
  press,
25
- intervalZone,
26
- gapZone,
30
+ intervalComponent: IntervalComponent,
31
+ gapComponent: GapComponent,
27
32
  highlightSource,
28
33
  onIntervalHover,
29
34
  incompleteLabel = 'Availability may be incomplete',
@@ -47,15 +52,15 @@ export function LaneRow({
47
52
  .filter((rect) => rect.layerId === layer.id)
48
53
  .map((rect) => (
49
54
  <Fragment key={`interval-${rect.x}:${rect.width}`}>
50
- {intervalZone({
51
- rect,
52
- layer,
53
- lane,
54
- highlighted: rect.sources.some(
55
+ <IntervalComponent
56
+ rect={rect}
57
+ layer={layer}
58
+ lane={lane}
59
+ highlighted={rect.sources.some(
55
60
  (source) =>
56
61
  source.kind === highlightSource?.kind && source.id === highlightSource.id,
57
- ),
58
- })}
62
+ )}
63
+ />
59
64
  </Fragment>
60
65
  ))}
61
66
  {geometry.gapRects
@@ -78,7 +83,7 @@ export function LaneRow({
78
83
  zIndex: rect.z,
79
84
  }}
80
85
  >
81
- {gapZone({ rect, layer, lane })}
86
+ <GapComponent rect={rect} layer={layer} lane={lane} />
82
87
  </Pressable>
83
88
  ))}
84
89
  </Fragment>