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.
- package/NOTICE.md +29 -0
- package/README.md +455 -2
- package/dist/src/adapters/rrule/cache.d.ts +15 -0
- package/dist/src/adapters/rrule/cache.d.ts.map +1 -0
- package/dist/src/adapters/rrule/cache.js +53 -0
- package/dist/src/adapters/rrule/cache.js.map +1 -0
- package/dist/src/adapters/rrule/envelope.d.ts +3 -0
- package/dist/src/adapters/rrule/envelope.d.ts.map +1 -0
- package/dist/src/adapters/rrule/envelope.js +9 -0
- package/dist/src/adapters/rrule/envelope.js.map +1 -0
- package/dist/src/adapters/rrule/expand.d.ts +4 -0
- package/dist/src/adapters/rrule/expand.d.ts.map +1 -0
- package/dist/src/adapters/rrule/expand.js +92 -0
- package/dist/src/adapters/rrule/expand.js.map +1 -0
- package/dist/src/adapters/rrule/hash.d.ts +3 -0
- package/dist/src/adapters/rrule/hash.d.ts.map +1 -0
- package/dist/src/adapters/rrule/hash.js +15 -0
- package/dist/src/adapters/rrule/hash.js.map +1 -0
- package/dist/src/adapters/rrule/index.d.ts +5 -0
- package/dist/src/adapters/rrule/index.d.ts.map +1 -0
- package/dist/src/adapters/rrule/index.js +12 -0
- package/dist/src/adapters/rrule/index.js.map +1 -0
- package/dist/src/adapters/rrule/net.d.ts +6 -0
- package/dist/src/adapters/rrule/net.d.ts.map +1 -0
- package/dist/src/adapters/rrule/net.js +31 -0
- package/dist/src/adapters/rrule/net.js.map +1 -0
- package/dist/src/adapters/rrule/occurrences.d.ts +6 -0
- package/dist/src/adapters/rrule/occurrences.d.ts.map +1 -0
- package/dist/src/adapters/rrule/occurrences.js +252 -0
- package/dist/src/adapters/rrule/occurrences.js.map +1 -0
- package/dist/src/adapters/rrule/types.d.ts +62 -0
- package/dist/src/adapters/rrule/types.d.ts.map +1 -0
- package/dist/src/adapters/rrule/types.js +3 -0
- package/dist/src/adapters/rrule/types.js.map +1 -0
- package/dist/src/adapters/rrule/validate.d.ts +6 -0
- package/dist/src/adapters/rrule/validate.d.ts.map +1 -0
- package/dist/src/adapters/rrule/validate.js +64 -0
- package/dist/src/adapters/rrule/validate.js.map +1 -0
- package/dist/src/components/layers/layers.types.d.ts +15 -0
- package/dist/src/components/layers/layers.types.d.ts.map +1 -0
- package/dist/src/components/layers/layers.types.js +3 -0
- package/dist/src/components/layers/layers.types.js.map +1 -0
- package/dist/src/components/layers/parts/gap.d.ts +3 -0
- package/dist/src/components/layers/parts/gap.d.ts.map +1 -0
- package/dist/src/components/layers/parts/gap.js +7 -0
- package/dist/src/components/layers/parts/gap.js.map +1 -0
- package/dist/src/components/layers/parts/interval.d.ts +3 -0
- package/dist/src/components/layers/parts/interval.d.ts.map +1 -0
- package/dist/src/components/layers/parts/interval.js +22 -0
- package/dist/src/components/layers/parts/interval.js.map +1 -0
- package/dist/src/components/layers/utils/styles.d.ts +7 -0
- package/dist/src/components/layers/utils/styles.d.ts.map +1 -0
- package/dist/src/components/layers/utils/styles.js +24 -0
- package/dist/src/components/layers/utils/styles.js.map +1 -0
- package/dist/src/components/primitives/press-point.d.ts +3 -0
- package/dist/src/components/primitives/press-point.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.js +7 -0
- package/dist/src/components/primitives/press-point.js.map +1 -0
- package/dist/src/components/primitives/press-point.types.d.ts +14 -0
- package/dist/src/components/primitives/press-point.types.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.types.js +3 -0
- package/dist/src/components/primitives/press-point.types.js.map +1 -0
- package/dist/src/components/primitives/press-point.web.d.ts +3 -0
- package/dist/src/components/primitives/press-point.web.d.ts.map +1 -0
- package/dist/src/components/primitives/press-point.web.js +17 -0
- package/dist/src/components/primitives/press-point.web.js.map +1 -0
- package/dist/src/components/primitives/region-style.d.ts +9 -0
- package/dist/src/components/primitives/region-style.d.ts.map +1 -0
- package/dist/src/components/primitives/region-style.js +20 -0
- package/dist/src/components/primitives/region-style.js.map +1 -0
- package/dist/src/components/roster/body-layout.d.ts +9 -0
- package/dist/src/components/roster/body-layout.d.ts.map +1 -0
- package/dist/src/components/roster/body-layout.js +9 -0
- package/dist/src/components/roster/body-layout.js.map +1 -0
- package/dist/src/components/roster/index.d.ts +3 -0
- package/dist/src/components/roster/index.d.ts.map +1 -0
- package/dist/src/components/roster/index.js +40 -0
- package/dist/src/components/roster/index.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.d.ts +3 -0
- package/dist/src/components/roster/lanes/interval-hover.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.js +7 -0
- package/dist/src/components/roster/lanes/interval-hover.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.types.d.ts +15 -0
- package/dist/src/components/roster/lanes/interval-hover.types.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.types.js +3 -0
- package/dist/src/components/roster/lanes/interval-hover.types.js.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.web.d.ts +3 -0
- package/dist/src/components/roster/lanes/interval-hover.web.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/interval-hover.web.js +20 -0
- package/dist/src/components/roster/lanes/interval-hover.web.js.map +1 -0
- package/dist/src/components/roster/lanes/lane.d.ts +10 -0
- package/dist/src/components/roster/lanes/lane.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/lane.js +32 -0
- package/dist/src/components/roster/lanes/lane.js.map +1 -0
- package/dist/src/components/roster/lanes/parts/incomplete.d.ts +8 -0
- package/dist/src/components/roster/lanes/parts/incomplete.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/parts/incomplete.js +30 -0
- package/dist/src/components/roster/lanes/parts/incomplete.js.map +1 -0
- package/dist/src/components/roster/lanes/parts/lane-label.d.ts +3 -0
- package/dist/src/components/roster/lanes/parts/lane-label.d.ts.map +1 -0
- package/dist/src/components/roster/lanes/parts/lane-label.js +18 -0
- package/dist/src/components/roster/lanes/parts/lane-label.js.map +1 -0
- package/dist/src/components/roster/layout.d.ts +17 -0
- package/dist/src/components/roster/layout.d.ts.map +1 -0
- package/dist/src/components/roster/layout.js +24 -0
- package/dist/src/components/roster/layout.js.map +1 -0
- package/dist/src/components/roster/parts/body.d.ts +7 -0
- package/dist/src/components/roster/parts/body.d.ts.map +1 -0
- package/dist/src/components/roster/parts/body.js +14 -0
- package/dist/src/components/roster/parts/body.js.map +1 -0
- package/dist/src/components/roster/parts/corner.d.ts +2 -0
- package/dist/src/components/roster/parts/corner.d.ts.map +1 -0
- package/dist/src/components/roster/parts/corner.js +7 -0
- package/dist/src/components/roster/parts/corner.js.map +1 -0
- package/dist/src/components/roster/parts/empty.d.ts +2 -0
- package/dist/src/components/roster/parts/empty.d.ts.map +1 -0
- package/dist/src/components/roster/parts/empty.js +9 -0
- package/dist/src/components/roster/parts/empty.js.map +1 -0
- package/dist/src/components/roster/parts/grid.d.ts +3 -0
- package/dist/src/components/roster/parts/grid.d.ts.map +1 -0
- package/dist/src/components/roster/parts/grid.js +16 -0
- package/dist/src/components/roster/parts/grid.js.map +1 -0
- package/dist/src/components/roster/parts/header-cell.d.ts +3 -0
- package/dist/src/components/roster/parts/header-cell.d.ts.map +1 -0
- package/dist/src/components/roster/parts/header-cell.js +9 -0
- package/dist/src/components/roster/parts/header-cell.js.map +1 -0
- package/dist/src/components/roster/parts/header.d.ts +3 -0
- package/dist/src/components/roster/parts/header.d.ts.map +1 -0
- package/dist/src/components/roster/parts/header.js +13 -0
- package/dist/src/components/roster/parts/header.js.map +1 -0
- package/dist/src/components/roster/parts/lane-label-column.d.ts +3 -0
- package/dist/src/components/roster/parts/lane-label-column.d.ts.map +1 -0
- package/dist/src/components/roster/parts/lane-label-column.js +18 -0
- package/dist/src/components/roster/parts/lane-label-column.js.map +1 -0
- package/dist/src/components/roster/parts/lane-list.d.ts +7 -0
- package/dist/src/components/roster/parts/lane-list.d.ts.map +1 -0
- package/dist/src/components/roster/parts/lane-list.js +25 -0
- package/dist/src/components/roster/parts/lane-list.js.map +1 -0
- package/dist/src/components/roster/roster.types.d.ts +145 -0
- package/dist/src/components/roster/roster.types.d.ts.map +1 -0
- package/dist/src/components/roster/roster.types.js +3 -0
- package/dist/src/components/roster/roster.types.js.map +1 -0
- package/dist/src/components/roster/use-roster.d.ts +3 -0
- package/dist/src/components/roster/use-roster.d.ts.map +1 -0
- package/dist/src/components/roster/use-roster.js +106 -0
- package/dist/src/components/roster/use-roster.js.map +1 -0
- package/dist/src/components/roster/utils/body-content-key.d.ts +3 -0
- package/dist/src/components/roster/utils/body-content-key.d.ts.map +1 -0
- package/dist/src/components/roster/utils/body-content-key.js +29 -0
- package/dist/src/components/roster/utils/body-content-key.js.map +1 -0
- package/dist/src/components/roster/utils/ticks.d.ts +4 -0
- package/dist/src/components/roster/utils/ticks.d.ts.map +1 -0
- package/dist/src/components/roster/utils/ticks.js +60 -0
- package/dist/src/components/roster/utils/ticks.js.map +1 -0
- package/dist/src/components/schedule/days/day.d.ts +20 -0
- package/dist/src/components/schedule/days/day.d.ts.map +1 -0
- package/dist/src/components/schedule/days/day.js +17 -0
- package/dist/src/components/schedule/days/day.js.map +1 -0
- package/dist/src/components/schedule/days/header-layout.d.ts +9 -0
- package/dist/src/components/schedule/days/header-layout.d.ts.map +1 -0
- package/dist/src/components/schedule/days/header-layout.js +9 -0
- package/dist/src/components/schedule/days/header-layout.js.map +1 -0
- package/dist/src/components/schedule/days/layout.d.ts +18 -0
- package/dist/src/components/schedule/days/layout.d.ts.map +1 -0
- package/dist/src/components/schedule/days/layout.js +9 -0
- package/dist/src/components/schedule/days/layout.js.map +1 -0
- package/dist/src/components/schedule/days/parts/column.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/column.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/column.js +28 -0
- package/dist/src/components/schedule/days/parts/column.js.map +1 -0
- package/dist/src/components/schedule/days/parts/day-header.d.ts +5 -0
- package/dist/src/components/schedule/days/parts/day-header.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/day-header.js +10 -0
- package/dist/src/components/schedule/days/parts/day-header.js.map +1 -0
- package/dist/src/components/schedule/days/parts/grid.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/grid.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/grid.js +14 -0
- package/dist/src/components/schedule/days/parts/grid.js.map +1 -0
- package/dist/src/components/schedule/days/parts/now-line.d.ts +5 -0
- package/dist/src/components/schedule/days/parts/now-line.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/now-line.js +9 -0
- package/dist/src/components/schedule/days/parts/now-line.js.map +1 -0
- package/dist/src/components/schedule/days/parts/skipped-date.d.ts +4 -0
- package/dist/src/components/schedule/days/parts/skipped-date.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/skipped-date.js +17 -0
- package/dist/src/components/schedule/days/parts/skipped-date.js.map +1 -0
- package/dist/src/components/schedule/days/parts/transition.d.ts +3 -0
- package/dist/src/components/schedule/days/parts/transition.d.ts.map +1 -0
- package/dist/src/components/schedule/days/parts/transition.js +42 -0
- package/dist/src/components/schedule/days/parts/transition.js.map +1 -0
- package/dist/src/components/schedule/index.d.ts +3 -0
- package/dist/src/components/schedule/index.d.ts.map +1 -0
- package/dist/src/components/schedule/index.js +44 -0
- package/dist/src/components/schedule/index.js.map +1 -0
- package/dist/src/components/schedule/layout.d.ts +15 -0
- package/dist/src/components/schedule/layout.d.ts.map +1 -0
- package/dist/src/components/schedule/layout.js +10 -0
- package/dist/src/components/schedule/layout.js.map +1 -0
- package/dist/src/components/schedule/parts/gutter.d.ts +3 -0
- package/dist/src/components/schedule/parts/gutter.d.ts.map +1 -0
- package/dist/src/components/schedule/parts/gutter.js +9 -0
- package/dist/src/components/schedule/parts/gutter.js.map +1 -0
- package/dist/src/components/schedule/parts/incomplete.d.ts +6 -0
- package/dist/src/components/schedule/parts/incomplete.d.ts.map +1 -0
- package/dist/src/components/schedule/parts/incomplete.js +9 -0
- package/dist/src/components/schedule/parts/incomplete.js.map +1 -0
- package/dist/src/components/schedule/schedule.types.d.ts +109 -0
- package/dist/src/components/schedule/schedule.types.d.ts.map +1 -0
- package/dist/src/components/schedule/schedule.types.js +3 -0
- package/dist/src/components/schedule/schedule.types.js.map +1 -0
- package/dist/src/components/schedule/use-schedule-viewport.d.ts +7 -0
- package/dist/src/components/schedule/use-schedule-viewport.d.ts.map +1 -0
- package/dist/src/components/schedule/use-schedule-viewport.js +15 -0
- package/dist/src/components/schedule/use-schedule-viewport.js.map +1 -0
- package/dist/src/components/schedule/use-schedule.d.ts +3 -0
- package/dist/src/components/schedule/use-schedule.d.ts.map +1 -0
- package/dist/src/components/schedule/use-schedule.js +71 -0
- package/dist/src/components/schedule/use-schedule.js.map +1 -0
- package/dist/src/components/schedule/utils/days.d.ts +17 -0
- package/dist/src/components/schedule/utils/days.d.ts.map +1 -0
- package/dist/src/components/schedule/utils/days.js +49 -0
- package/dist/src/components/schedule/utils/days.js.map +1 -0
- package/dist/src/core/axis.d.ts +18 -0
- package/dist/src/core/axis.d.ts.map +1 -0
- package/dist/src/core/axis.js +58 -0
- package/dist/src/core/axis.js.map +1 -0
- package/dist/src/core/cache.d.ts +29 -0
- package/dist/src/core/cache.d.ts.map +1 -0
- package/dist/src/core/cache.js +33 -0
- package/dist/src/core/cache.js.map +1 -0
- package/dist/src/core/columns.d.ts +3 -0
- package/dist/src/core/columns.d.ts.map +1 -0
- package/dist/src/core/columns.js +45 -0
- package/dist/src/core/columns.js.map +1 -0
- package/dist/src/core/coverage.d.ts +3 -0
- package/dist/src/core/coverage.d.ts.map +1 -0
- package/dist/src/core/coverage.js +60 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/flag.d.ts +3 -0
- package/dist/src/core/flag.d.ts.map +1 -0
- package/dist/src/core/flag.js +17 -0
- package/dist/src/core/flag.js.map +1 -0
- package/dist/src/core/hash.d.ts +5 -0
- package/dist/src/core/hash.d.ts.map +1 -0
- package/dist/src/core/hash.js +29 -0
- package/dist/src/core/hash.js.map +1 -0
- package/dist/src/core/hit-test.d.ts +6 -0
- package/dist/src/core/hit-test.d.ts.map +1 -0
- package/dist/src/core/hit-test.js +23 -0
- package/dist/src/core/hit-test.js.map +1 -0
- package/dist/src/core/index.d.ts +12 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +30 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/layout.d.ts +3 -0
- package/dist/src/core/layout.d.ts.map +1 -0
- package/dist/src/core/layout.js +96 -0
- package/dist/src/core/layout.js.map +1 -0
- package/dist/src/core/order.d.ts +7 -0
- package/dist/src/core/order.d.ts.map +1 -0
- package/dist/src/core/order.js +12 -0
- package/dist/src/core/order.js.map +1 -0
- package/dist/src/core/scale.d.ts +9 -0
- package/dist/src/core/scale.d.ts.map +1 -0
- package/dist/src/core/scale.js +41 -0
- package/dist/src/core/scale.js.map +1 -0
- package/dist/src/core/snap.d.ts +10 -0
- package/dist/src/core/snap.d.ts.map +1 -0
- package/dist/src/core/snap.js +43 -0
- package/dist/src/core/snap.js.map +1 -0
- package/dist/src/core/spans.d.ts +3 -0
- package/dist/src/core/spans.d.ts.map +1 -0
- package/dist/src/core/spans.js +60 -0
- package/dist/src/core/spans.js.map +1 -0
- package/dist/src/core/types.d.ts +104 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +3 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/zone.d.ts +10 -0
- package/dist/src/core/zone.d.ts.map +1 -0
- package/dist/src/core/zone.js +103 -0
- package/dist/src/core/zone.js.map +1 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +67 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/nativewind/index.d.ts +59 -0
- package/dist/src/nativewind/index.d.ts.map +1 -0
- package/dist/src/nativewind/index.js +24 -0
- package/dist/src/nativewind/index.js.map +1 -0
- package/docs/migrations.md +61 -0
- package/llms.txt +498 -0
- package/package.json +155 -8
- package/src/adapters/rrule/README.md +41 -0
- package/src/adapters/rrule/cache.ts +50 -0
- package/src/adapters/rrule/envelope.ts +7 -0
- package/src/adapters/rrule/expand.ts +105 -0
- package/src/adapters/rrule/hash.ts +13 -0
- package/src/adapters/rrule/index.ts +11 -0
- package/src/adapters/rrule/net.ts +30 -0
- package/src/adapters/rrule/occurrences.ts +288 -0
- package/src/adapters/rrule/types.ts +52 -0
- package/src/adapters/rrule/validate.ts +78 -0
- package/src/components/layers/README.md +15 -0
- package/src/components/layers/layers.types.ts +6 -0
- package/src/components/layers/parts/gap.tsx +5 -0
- package/src/components/layers/parts/interval.tsx +24 -0
- package/src/components/layers/utils/styles.ts +24 -0
- package/src/components/primitives/README.md +12 -0
- package/src/components/primitives/press-point.tsx +5 -0
- package/src/components/primitives/press-point.types.ts +5 -0
- package/src/components/primitives/press-point.web.tsx +19 -0
- package/src/components/primitives/region-style.ts +21 -0
- package/src/components/roster/README.md +29 -0
- package/src/components/roster/body-layout.tsx +32 -0
- package/src/components/roster/index.tsx +108 -0
- package/src/components/roster/lanes/interval-hover.tsx +5 -0
- package/src/components/roster/lanes/interval-hover.types.ts +11 -0
- package/src/components/roster/lanes/interval-hover.web.tsx +19 -0
- package/src/components/roster/lanes/lane.tsx +93 -0
- package/src/components/roster/lanes/parts/incomplete.tsx +44 -0
- package/src/components/roster/lanes/parts/lane-label.tsx +41 -0
- package/src/components/roster/layout.tsx +68 -0
- package/src/components/roster/parts/body.tsx +24 -0
- package/src/components/roster/parts/corner.tsx +3 -0
- package/src/components/roster/parts/empty.tsx +5 -0
- package/src/components/roster/parts/grid.tsx +26 -0
- package/src/components/roster/parts/header-cell.tsx +10 -0
- package/src/components/roster/parts/header.tsx +23 -0
- package/src/components/roster/parts/lane-label-column.tsx +31 -0
- package/src/components/roster/parts/lane-list.tsx +74 -0
- package/src/components/roster/roster.types.ts +156 -0
- package/src/components/roster/use-roster.ts +133 -0
- package/src/components/roster/utils/body-content-key.ts +46 -0
- package/src/components/roster/utils/ticks.ts +62 -0
- package/src/components/schedule/README.md +23 -0
- package/src/components/schedule/days/day.tsx +89 -0
- package/src/components/schedule/days/header-layout.tsx +12 -0
- package/src/components/schedule/days/layout.tsx +38 -0
- package/src/components/schedule/days/parts/column.tsx +59 -0
- package/src/components/schedule/days/parts/day-header.tsx +18 -0
- package/src/components/schedule/days/parts/grid.tsx +21 -0
- package/src/components/schedule/days/parts/now-line.tsx +11 -0
- package/src/components/schedule/days/parts/skipped-date.tsx +22 -0
- package/src/components/schedule/days/parts/transition.tsx +70 -0
- package/src/components/schedule/index.tsx +108 -0
- package/src/components/schedule/layout.tsx +42 -0
- package/src/components/schedule/parts/gutter.tsx +16 -0
- package/src/components/schedule/parts/incomplete.tsx +13 -0
- package/src/components/schedule/schedule.types.ts +103 -0
- package/src/components/schedule/use-schedule-viewport.ts +13 -0
- package/src/components/schedule/use-schedule.ts +75 -0
- package/src/components/schedule/utils/days.ts +54 -0
- package/src/core/README.md +25 -0
- package/src/core/axis.ts +58 -0
- package/src/core/cache.ts +38 -0
- package/src/core/columns.ts +49 -0
- package/src/core/coverage.ts +56 -0
- package/src/core/flag.ts +14 -0
- package/src/core/hash.ts +28 -0
- package/src/core/hit-test.ts +27 -0
- package/src/core/index.ts +34 -0
- package/src/core/layout.ts +101 -0
- package/src/core/order.ts +18 -0
- package/src/core/scale.ts +45 -0
- package/src/core/snap.ts +52 -0
- package/src/core/spans.ts +58 -0
- package/src/core/types.ts +92 -0
- package/src/core/zone.ts +100 -0
- package/src/index.ts +52 -0
- package/src/nativewind/README.md +14 -0
- package/src/nativewind/index.ts +30 -0
- 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.
|