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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode, RefObject } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
LayoutChangeEvent,
|
|
4
|
+
NativeScrollEvent,
|
|
5
|
+
NativeSyntheticEvent,
|
|
6
|
+
ScrollView,
|
|
7
|
+
StyleProp,
|
|
8
|
+
ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
11
|
+
import type {
|
|
12
|
+
Coverage,
|
|
13
|
+
Lane,
|
|
14
|
+
LaneComparator,
|
|
15
|
+
LaneFlag,
|
|
16
|
+
LaneGeometry,
|
|
17
|
+
Projection,
|
|
18
|
+
Rect,
|
|
19
|
+
Source,
|
|
20
|
+
Window,
|
|
21
|
+
WindowSpec,
|
|
22
|
+
} from '../../core';
|
|
23
|
+
import type { GapInput, IntervalInput } from '../layers/layers.types';
|
|
24
|
+
|
|
25
|
+
export type RosterProjection = Extract<Projection, { orientation: 'horizontal' }>;
|
|
26
|
+
export type RosterTick = { time: number; x: number; label: string; kind: 'day' | 'time' };
|
|
27
|
+
export type RosterScroll = {
|
|
28
|
+
x: SharedValue<number>;
|
|
29
|
+
y: SharedValue<number>;
|
|
30
|
+
bodyRef: RefObject<ScrollView | null>;
|
|
31
|
+
onBodyScroll: (input: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
32
|
+
onHeaderScroll: (input: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
33
|
+
onVerticalScroll: (input: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
34
|
+
headerStyle: StyleProp<ViewStyle>;
|
|
35
|
+
labelStyle: StyleProp<ViewStyle>;
|
|
36
|
+
};
|
|
37
|
+
export type RosterInput = {
|
|
38
|
+
lanes: Lane[];
|
|
39
|
+
windowSpec: WindowSpec;
|
|
40
|
+
minuteStep?: number;
|
|
41
|
+
sortLanes?: LaneComparator;
|
|
42
|
+
highlightSource?: Source;
|
|
43
|
+
onNavigate?: (next: WindowSpec) => void;
|
|
44
|
+
onIntervalPress?: (rect: Rect, lane: Lane) => void;
|
|
45
|
+
/** Reports the winning interval under a web pointer; native renders attach no hover handler. */
|
|
46
|
+
onIntervalHover?: (rect: Rect, lane: Lane) => void;
|
|
47
|
+
onGapPress?: (rect: Rect, lane: Lane) => void;
|
|
48
|
+
onCellPress?: (lane: Lane, time: number) => void;
|
|
49
|
+
/** Fixed lane height, default 48. */
|
|
50
|
+
rowHeight?: number;
|
|
51
|
+
/** Minimum elapsed-time scale, default 0.5; expands to fit wider viewports. */
|
|
52
|
+
pxPerMinute?: number;
|
|
53
|
+
};
|
|
54
|
+
export type RosterModel = {
|
|
55
|
+
window: Window;
|
|
56
|
+
projection: RosterProjection;
|
|
57
|
+
orderedLanes: Lane[];
|
|
58
|
+
coverage: ReadonlyMap<string, Coverage>;
|
|
59
|
+
laneState: ReadonlyMap<string, { flag: LaneFlag; complete: boolean }>;
|
|
60
|
+
geometryFor: (lane: Lane) => LaneGeometry;
|
|
61
|
+
scroll: RosterScroll;
|
|
62
|
+
press: (lane: Lane, x: number, y: number) => void;
|
|
63
|
+
status: 'empty' | 'ready';
|
|
64
|
+
ticks: RosterTick[];
|
|
65
|
+
contentWidth: number;
|
|
66
|
+
viewport: { width: number; height: number };
|
|
67
|
+
onLayout: (input: LayoutChangeEvent) => void;
|
|
68
|
+
navigate: (next: WindowSpec) => void;
|
|
69
|
+
};
|
|
70
|
+
export type LaneLabelInput = {
|
|
71
|
+
lane: Lane;
|
|
72
|
+
flag: LaneFlag;
|
|
73
|
+
complete: boolean;
|
|
74
|
+
viewTimezone: string;
|
|
75
|
+
incompleteLabel: string;
|
|
76
|
+
neverSetLabel: string;
|
|
77
|
+
};
|
|
78
|
+
export type HeaderCellInput = { tick: RosterTick };
|
|
79
|
+
export type GridInput = { ticks: RosterTick[]; contentWidth: number };
|
|
80
|
+
export type HeaderInput = Pick<RosterModel, 'ticks' | 'projection' | 'scroll' | 'contentWidth'> & {
|
|
81
|
+
/** Time label filler, positioned by the header. */
|
|
82
|
+
headerCellComponent: ComponentType<HeaderCellInput>;
|
|
83
|
+
};
|
|
84
|
+
export type LabelColumnInput = Pick<RosterModel, 'projection' | 'scroll'> & {
|
|
85
|
+
labels: LaneLabelInput[];
|
|
86
|
+
/** Lane label filler, positioned at the fixed row height. */
|
|
87
|
+
laneLabelComponent: ComponentType<LaneLabelInput>;
|
|
88
|
+
};
|
|
89
|
+
export type BodyInput = Pick<
|
|
90
|
+
RosterModel,
|
|
91
|
+
| 'geometryFor'
|
|
92
|
+
| 'projection'
|
|
93
|
+
| 'scroll'
|
|
94
|
+
| 'press'
|
|
95
|
+
| 'ticks'
|
|
96
|
+
| 'contentWidth'
|
|
97
|
+
| 'viewport'
|
|
98
|
+
| 'window'
|
|
99
|
+
> & {
|
|
100
|
+
lanes: Lane[];
|
|
101
|
+
highlightSource?: Source;
|
|
102
|
+
onIntervalHover?: (rect: Rect, lane: Lane) => void;
|
|
103
|
+
incompleteLabel?: string;
|
|
104
|
+
/** Covered rect view; position it using the final rect bounds, as RosterInterval does. */
|
|
105
|
+
intervalComponent: ComponentType<IntervalInput>;
|
|
106
|
+
/** Removed rect filler, placed inside a pressable by LaneRow. */
|
|
107
|
+
gapComponent: ComponentType<GapInput>;
|
|
108
|
+
/** Noninteractive tick lines behind every lane, sized to the content width. */
|
|
109
|
+
gridComponent: ComponentType<GridInput>;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Chrome style props and their NativeWind class twins. Class props resolve only
|
|
113
|
+
* after `react-native-roster/nativewind` registers the component; without that
|
|
114
|
+
* entry they are ignored.
|
|
115
|
+
*/
|
|
116
|
+
export type RosterStyleProps = {
|
|
117
|
+
/** Outer container; supply a bounded height. */
|
|
118
|
+
style?: StyleProp<ViewStyle>;
|
|
119
|
+
className?: string;
|
|
120
|
+
/** Frozen header row holding the corner and tick strip, 40 px tall by default. */
|
|
121
|
+
headerStyle?: StyleProp<ViewStyle>;
|
|
122
|
+
headerClassName?: string;
|
|
123
|
+
/** Frozen lane label column, laneLabelWidth wide. */
|
|
124
|
+
laneLabelColumnStyle?: StyleProp<ViewStyle>;
|
|
125
|
+
laneLabelColumnClassName?: string;
|
|
126
|
+
/** Measured body viewport containing the virtualized lanes. */
|
|
127
|
+
bodyStyle?: StyleProp<ViewStyle>;
|
|
128
|
+
bodyClassName?: string;
|
|
129
|
+
/** Width of the corner and lane label column, default 180. */
|
|
130
|
+
laneLabelWidth?: number;
|
|
131
|
+
};
|
|
132
|
+
export type RosterProps = RosterInput &
|
|
133
|
+
RosterStyleProps & {
|
|
134
|
+
incompleteLabel?: string;
|
|
135
|
+
neverSetLabel?: string;
|
|
136
|
+
/** Label, differing lane zone, effective flag, and completeness notice. */
|
|
137
|
+
laneLabelComponent?: ComponentType<LaneLabelInput>;
|
|
138
|
+
/** Time label within one positioned header tick. */
|
|
139
|
+
headerCellComponent?: ComponentType<HeaderCellInput>;
|
|
140
|
+
/** Covered rect view; use rect bounds for absolute position, rect.z for stacking, and pointerEvents="none" for row hit testing. */
|
|
141
|
+
intervalComponent?: ComponentType<IntervalInput>;
|
|
142
|
+
/** Removed rect content; LaneRow supplies the invisible pressable. */
|
|
143
|
+
gapComponent?: ComponentType<GapInput>;
|
|
144
|
+
/** Tick lines behind the lanes; RosterGrid draws one hairline per tick. */
|
|
145
|
+
gridComponent?: ComponentType<GridInput>;
|
|
146
|
+
/** Empty roster content, conventionally the text No lanes. */
|
|
147
|
+
emptyZone?: ReactNode;
|
|
148
|
+
/** Top-left cell above the lane labels; RosterCorner renders nothing. */
|
|
149
|
+
cornerZone?: ReactNode;
|
|
150
|
+
/** Frozen time header, supplied with ticks, scale, and shared scrolling. */
|
|
151
|
+
headerComponent?: ComponentType<HeaderInput>;
|
|
152
|
+
/** Frozen labels, supplied with ordered lanes and per-lane flag and completeness. */
|
|
153
|
+
laneLabelColumnComponent?: ComponentType<LabelColumnInput>;
|
|
154
|
+
/** Virtualized lane body, supplied with geometry, projection, scrolling, and press resolution. */
|
|
155
|
+
bodyComponent?: ComponentType<BodyInput>;
|
|
156
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import type { ScrollView } from 'react-native';
|
|
3
|
+
import { makeMutable, useAnimatedStyle } from 'react-native-reanimated';
|
|
4
|
+
import type { Lane, LaneGeometry } from '../../core';
|
|
5
|
+
import {
|
|
6
|
+
byLabel,
|
|
7
|
+
coverageFor,
|
|
8
|
+
flagFor,
|
|
9
|
+
layoutLane,
|
|
10
|
+
snapToStep,
|
|
11
|
+
timeAtX,
|
|
12
|
+
windowFor,
|
|
13
|
+
} from '../../core';
|
|
14
|
+
import { hitTest } from '../../core/hit-test';
|
|
15
|
+
import type { RosterInput, RosterModel, RosterProjection } from './roster.types';
|
|
16
|
+
import { ticksFor } from './utils/ticks';
|
|
17
|
+
|
|
18
|
+
export function useRoster(input: RosterInput): RosterModel {
|
|
19
|
+
const {
|
|
20
|
+
lanes,
|
|
21
|
+
windowSpec,
|
|
22
|
+
minuteStep = 60,
|
|
23
|
+
sortLanes = byLabel,
|
|
24
|
+
rowHeight = 48,
|
|
25
|
+
pxPerMinute = 0.5,
|
|
26
|
+
onNavigate,
|
|
27
|
+
} = input;
|
|
28
|
+
const [viewport, setViewport] = useState({ width: 0, height: 0 });
|
|
29
|
+
// The pinned compiler lint cannot resolve useSharedValue's built-in type.
|
|
30
|
+
// These shared values only track offsets, so no animation needs cancellation.
|
|
31
|
+
const [x] = useState(() => makeMutable(0));
|
|
32
|
+
const [y] = useState(() => makeMutable(0));
|
|
33
|
+
const bodyRef = useRef<ScrollView>(null);
|
|
34
|
+
const headerStyle = useAnimatedStyle(() => ({ transform: [{ translateX: -x.get() }] }));
|
|
35
|
+
const labelStyle = useAnimatedStyle(() => ({ transform: [{ translateY: -y.get() }] }));
|
|
36
|
+
const window = windowFor(windowSpec);
|
|
37
|
+
if (
|
|
38
|
+
!(rowHeight > 0) ||
|
|
39
|
+
!Number.isFinite(rowHeight) ||
|
|
40
|
+
!(pxPerMinute > 0) ||
|
|
41
|
+
!Number.isFinite(pxPerMinute)
|
|
42
|
+
)
|
|
43
|
+
throw new RangeError('rowHeight and pxPerMinute must be positive finite numbers');
|
|
44
|
+
const duration = (window.end - window.start) / 60_000;
|
|
45
|
+
const projection: RosterProjection = {
|
|
46
|
+
orientation: 'horizontal',
|
|
47
|
+
viewTimezone: windowSpec.timezone,
|
|
48
|
+
rowHeight,
|
|
49
|
+
pxPerMinute: Math.max(pxPerMinute, duration === 0 ? 0 : viewport.width / duration),
|
|
50
|
+
};
|
|
51
|
+
const contentWidth = duration * projection.pxPerMinute;
|
|
52
|
+
const coverage = new Map(lanes.map((lane) => [lane.id, coverageFor(lane, window)]));
|
|
53
|
+
const orderedLanes = [...lanes].sort((a, b) => sortLanes(a, b, coverage));
|
|
54
|
+
const laneState = new Map(
|
|
55
|
+
lanes.map((lane) => [
|
|
56
|
+
lane.id,
|
|
57
|
+
{ flag: flagFor(lane, window), complete: lane.complete ?? true },
|
|
58
|
+
]),
|
|
59
|
+
);
|
|
60
|
+
function geometryFor(lane: Lane): LaneGeometry {
|
|
61
|
+
return layoutLane(lane, window, projection);
|
|
62
|
+
}
|
|
63
|
+
const currentPress = { input, window, projection, width: contentWidth };
|
|
64
|
+
const pressInput = useRef(currentPress);
|
|
65
|
+
pressInput.current = currentPress;
|
|
66
|
+
const [press] = useState(() => {
|
|
67
|
+
return function press(lane: Lane, pointX: number, pointY: number): void {
|
|
68
|
+
const { input, window, projection, width } = pressInput.current;
|
|
69
|
+
const { minuteStep = 60, onIntervalPress, onGapPress, onCellPress } = input;
|
|
70
|
+
const { rowHeight, viewTimezone } = projection;
|
|
71
|
+
if (
|
|
72
|
+
!Number.isFinite(pointX) ||
|
|
73
|
+
!Number.isFinite(pointY) ||
|
|
74
|
+
pointX < 0 ||
|
|
75
|
+
pointX >= width ||
|
|
76
|
+
pointY < 0 ||
|
|
77
|
+
pointY >= rowHeight
|
|
78
|
+
)
|
|
79
|
+
return;
|
|
80
|
+
const hit = hitTest(lane, layoutLane(lane, window, projection), pointX, pointY);
|
|
81
|
+
if (hit?.kind === 'interval') {
|
|
82
|
+
onIntervalPress?.(hit.rect, lane);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (hit?.kind === 'gap') {
|
|
86
|
+
onGapPress?.(hit.rect, lane);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const time = Math.max(
|
|
90
|
+
window.start,
|
|
91
|
+
snapToStep(timeAtX(projection, window, pointX), minuteStep, viewTimezone),
|
|
92
|
+
);
|
|
93
|
+
if (time < window.end) onCellPress?.(lane, time);
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
window,
|
|
98
|
+
projection,
|
|
99
|
+
orderedLanes,
|
|
100
|
+
coverage,
|
|
101
|
+
laneState,
|
|
102
|
+
geometryFor,
|
|
103
|
+
press,
|
|
104
|
+
status: lanes.length === 0 ? 'empty' : 'ready',
|
|
105
|
+
ticks: ticksFor(window, windowSpec, projection, minuteStep),
|
|
106
|
+
contentWidth,
|
|
107
|
+
viewport,
|
|
108
|
+
navigate: (next) => onNavigate?.(next),
|
|
109
|
+
onLayout: ({ nativeEvent: { layout } }) =>
|
|
110
|
+
setViewport((previous) =>
|
|
111
|
+
previous.width === layout.width && previous.height === layout.height
|
|
112
|
+
? previous
|
|
113
|
+
: { width: layout.width, height: layout.height },
|
|
114
|
+
),
|
|
115
|
+
scroll: {
|
|
116
|
+
x,
|
|
117
|
+
y,
|
|
118
|
+
bodyRef,
|
|
119
|
+
headerStyle,
|
|
120
|
+
labelStyle,
|
|
121
|
+
onBodyScroll: ({ nativeEvent }) => {
|
|
122
|
+
x.set(nativeEvent.contentOffset.x);
|
|
123
|
+
},
|
|
124
|
+
onHeaderScroll: ({ nativeEvent }) => {
|
|
125
|
+
x.set(nativeEvent.contentOffset.x);
|
|
126
|
+
bodyRef.current?.scrollTo({ x: nativeEvent.contentOffset.x, animated: false });
|
|
127
|
+
},
|
|
128
|
+
onVerticalScroll: ({ nativeEvent }) => {
|
|
129
|
+
y.set(nativeEvent.contentOffset.y);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { BodyInput } from '../roster.types';
|
|
2
|
+
|
|
3
|
+
const identityIds = new WeakMap<object, number>();
|
|
4
|
+
let nextIdentityId = 0;
|
|
5
|
+
|
|
6
|
+
function identityId(identity: object): number {
|
|
7
|
+
let id = identityIds.get(identity);
|
|
8
|
+
if (id === undefined) {
|
|
9
|
+
id = nextIdentityId++;
|
|
10
|
+
identityIds.set(identity, id);
|
|
11
|
+
}
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function bodyContentKey({
|
|
16
|
+
window,
|
|
17
|
+
projection,
|
|
18
|
+
highlightSource,
|
|
19
|
+
intervalComponent,
|
|
20
|
+
gapComponent,
|
|
21
|
+
onIntervalHover,
|
|
22
|
+
incompleteLabel,
|
|
23
|
+
}: Pick<
|
|
24
|
+
BodyInput,
|
|
25
|
+
| 'window'
|
|
26
|
+
| 'projection'
|
|
27
|
+
| 'highlightSource'
|
|
28
|
+
| 'intervalComponent'
|
|
29
|
+
| 'gapComponent'
|
|
30
|
+
| 'onIntervalHover'
|
|
31
|
+
| 'incompleteLabel'
|
|
32
|
+
>): string {
|
|
33
|
+
return JSON.stringify([
|
|
34
|
+
window.start,
|
|
35
|
+
window.end,
|
|
36
|
+
projection.viewTimezone,
|
|
37
|
+
projection.rowHeight,
|
|
38
|
+
projection.pxPerMinute,
|
|
39
|
+
highlightSource?.kind,
|
|
40
|
+
highlightSource?.id,
|
|
41
|
+
identityId(intervalComponent),
|
|
42
|
+
identityId(gapComponent),
|
|
43
|
+
onIntervalHover && identityId(onIntervalHover),
|
|
44
|
+
incompleteLabel,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Window, WindowSpec } from '../../../core';
|
|
2
|
+
import { dayColumnsFor } from '../../../core';
|
|
3
|
+
import { offsetAt } from '../../../core/zone';
|
|
4
|
+
import type { RosterProjection, RosterTick } from '../roster.types';
|
|
5
|
+
|
|
6
|
+
// Like day columns, ticks retain visited content keys for this runtime.
|
|
7
|
+
const tickCache = new Map<string, RosterTick[]>();
|
|
8
|
+
|
|
9
|
+
export function ticksFor(
|
|
10
|
+
window: Window,
|
|
11
|
+
spec: WindowSpec,
|
|
12
|
+
projection: RosterProjection,
|
|
13
|
+
minuteStep: number,
|
|
14
|
+
): RosterTick[] {
|
|
15
|
+
if (!Number.isInteger(minuteStep) || minuteStep <= 0 || 60 % minuteStep !== 0)
|
|
16
|
+
throw new RangeError('minuteStep must be a positive divisor of 60');
|
|
17
|
+
const key = JSON.stringify([
|
|
18
|
+
window.start,
|
|
19
|
+
window.end,
|
|
20
|
+
spec.timezone,
|
|
21
|
+
spec.span,
|
|
22
|
+
minuteStep,
|
|
23
|
+
projection.pxPerMinute,
|
|
24
|
+
]);
|
|
25
|
+
const cached = tickCache.get(key);
|
|
26
|
+
if (cached) return cached;
|
|
27
|
+
const ticks: RosterTick[] = [];
|
|
28
|
+
const step = minuteStep * 60_000;
|
|
29
|
+
function add(time: number, label: string, kind: RosterTick['kind']) {
|
|
30
|
+
ticks.push({ time, x: ((time - window.start) / 60_000) * projection.pxPerMinute, label, kind });
|
|
31
|
+
}
|
|
32
|
+
const timeLabel = new Intl.DateTimeFormat('en-US', {
|
|
33
|
+
timeZone: spec.timezone,
|
|
34
|
+
hour: '2-digit',
|
|
35
|
+
minute: '2-digit',
|
|
36
|
+
hourCycle: 'h23',
|
|
37
|
+
});
|
|
38
|
+
for (const day of dayColumnsFor(window, spec.timezone)) {
|
|
39
|
+
const start = Math.max(day.start, window.start);
|
|
40
|
+
add(start, `${day.label} ${day.localDate}`, 'day');
|
|
41
|
+
if (spec.span === 'month' || (spec.span === 'week' && minuteStep * projection.pxPerMinute < 40))
|
|
42
|
+
continue;
|
|
43
|
+
// Each transition starts a new constant-offset segment. Positive deltas skip
|
|
44
|
+
// wall minutes; negative deltas emit both absolute occurrences of a repeat.
|
|
45
|
+
let segmentStart = day.start;
|
|
46
|
+
// UTC wall coordinates retain the actual start minute even after a midnight skip.
|
|
47
|
+
let offset = offsetAt(day.start, spec.timezone);
|
|
48
|
+
for (const boundary of [...day.transitions, { at: day.end, deltaMinutes: 0 }]) {
|
|
49
|
+
const wall = Math.max(start, segmentStart) + offset;
|
|
50
|
+
const end = Math.min(boundary.at, window.end);
|
|
51
|
+
for (let time = Math.ceil(wall / step) * step - offset; time < end; time += step) {
|
|
52
|
+
if (time === start) continue;
|
|
53
|
+
add(time, timeLabel.format(time), 'time');
|
|
54
|
+
}
|
|
55
|
+
segmentStart = boundary.at;
|
|
56
|
+
// The origin already includes the part of a boundary skip after midnight.
|
|
57
|
+
if (boundary.at > day.start) offset += boundary.deltaMinutes * 60_000;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
tickCache.set(key, ticks);
|
|
61
|
+
return ticks;
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Schedule
|
|
2
|
+
|
|
3
|
+
This feature is about a schedule; its children are days.
|
|
4
|
+
|
|
5
|
+
- Root entry exports: `Schedule`, `useSchedule`, the `ScheduleXxx` parts, and
|
|
6
|
+
every `ScheduleXxxInput` slot input type from `schedule.types.ts`
|
|
7
|
+
|
|
8
|
+
- `index.tsx`: the chassis; measures the viewport, calls `useSchedule`, composes zones
|
|
9
|
+
- `use-schedule.ts`, `use-schedule-viewport.ts`: the hook and the width context
|
|
10
|
+
- `layout.tsx`: arranges header, gutter, and days regions only
|
|
11
|
+
- `schedule.types.ts`: props, model, and every slot input type
|
|
12
|
+
- `parts/`: collection-level parts (gutter, incomplete)
|
|
13
|
+
- `days/`: `ScheduleDay`, the day and day-header layouts, and day-local parts
|
|
14
|
+
- `utils/days.ts`: header dates, now position, and transition bounds
|
|
15
|
+
|
|
16
|
+
Interval and gap components live in `../layers`; press geometry and `regionStyle`
|
|
17
|
+
in `../primitives`. Zone contracts are documented in the README's Schedule
|
|
18
|
+
section and in `llms.txt`. Tests: `test/components/schedule`.
|
|
19
|
+
|
|
20
|
+
The chassis binds default component types once; ScheduleDay mounts components
|
|
21
|
+
with day data and passes nodes into ScheduleDayLayout. There is no black-box day
|
|
22
|
+
list to split: days, transitions, and rects already map in their owning parts.
|
|
23
|
+
Schedule has ten input-bearing component slots and no zero-argument singleton slots.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Fragment, type ReactNode } from 'react';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import type { DayColumn, Lane, LaneGeometry } from '../../../core';
|
|
4
|
+
import { pressPoint } from '../../primitives/press-point';
|
|
5
|
+
import type { ScheduleModel, ScheduleProps } from '../schedule.types';
|
|
6
|
+
import { transitionBounds } from '../utils/days';
|
|
7
|
+
import { ScheduleDayLayout } from './layout';
|
|
8
|
+
|
|
9
|
+
type ScheduleDayZones = Required<
|
|
10
|
+
Pick<
|
|
11
|
+
ScheduleProps,
|
|
12
|
+
| 'gridComponent'
|
|
13
|
+
| 'columnComponent'
|
|
14
|
+
| 'transitionComponent'
|
|
15
|
+
| 'intervalComponent'
|
|
16
|
+
| 'gapComponent'
|
|
17
|
+
>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
type ScheduleDayProps = ScheduleDayZones & {
|
|
21
|
+
day: DayColumn;
|
|
22
|
+
column: number;
|
|
23
|
+
lane: Lane;
|
|
24
|
+
projection: ScheduleModel['projection'];
|
|
25
|
+
geometry: LaneGeometry;
|
|
26
|
+
highlightSource: ScheduleProps['highlightSource'];
|
|
27
|
+
hours: number[];
|
|
28
|
+
press: (x: number, y: number) => void;
|
|
29
|
+
/** The current-time line when it falls on this day; otherwise nothing. */
|
|
30
|
+
nowLineZone: ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** One day column: grid, rects, transitions, and the now line at engine bounds. */
|
|
34
|
+
export function ScheduleDay({
|
|
35
|
+
day,
|
|
36
|
+
column,
|
|
37
|
+
lane,
|
|
38
|
+
projection,
|
|
39
|
+
geometry,
|
|
40
|
+
highlightSource,
|
|
41
|
+
hours,
|
|
42
|
+
press,
|
|
43
|
+
gridComponent: GridComponent,
|
|
44
|
+
columnComponent: ColumnComponent,
|
|
45
|
+
transitionComponent: TransitionComponent,
|
|
46
|
+
intervalComponent,
|
|
47
|
+
gapComponent,
|
|
48
|
+
nowLineZone,
|
|
49
|
+
}: ScheduleDayProps) {
|
|
50
|
+
return (
|
|
51
|
+
<Pressable
|
|
52
|
+
testID={`schedule-day-${day.localDate}`}
|
|
53
|
+
accessibilityLabel={day.localDate}
|
|
54
|
+
onPress={(input) => {
|
|
55
|
+
const point = pressPoint(input);
|
|
56
|
+
press(point.x, point.y);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<ScheduleDayLayout
|
|
60
|
+
width={projection.columnWidth}
|
|
61
|
+
height={24 * projection.pxPerHour}
|
|
62
|
+
chromeZ={Math.max(0, ...lane.layers.map((layer) => layer.z)) + 1}
|
|
63
|
+
gridZone={<GridComponent hours={hours} pxPerHour={projection.pxPerHour} />}
|
|
64
|
+
columnZone={
|
|
65
|
+
<ColumnComponent
|
|
66
|
+
day={day}
|
|
67
|
+
lane={lane}
|
|
68
|
+
rects={geometry.rects.filter((rect) => rect.column === column)}
|
|
69
|
+
gapRects={geometry.gapRects.filter((rect) => rect.column === column)}
|
|
70
|
+
highlightSource={highlightSource}
|
|
71
|
+
intervalComponent={intervalComponent}
|
|
72
|
+
gapComponent={gapComponent}
|
|
73
|
+
press={press}
|
|
74
|
+
/>
|
|
75
|
+
}
|
|
76
|
+
transitionZone={day.transitions.map((transition) => (
|
|
77
|
+
<Fragment key={transition.at}>
|
|
78
|
+
<TransitionComponent
|
|
79
|
+
day={day}
|
|
80
|
+
transition={transition}
|
|
81
|
+
{...transitionBounds(day, transition, projection)}
|
|
82
|
+
/>
|
|
83
|
+
</Fragment>
|
|
84
|
+
))}
|
|
85
|
+
nowLineZone={nowLineZone}
|
|
86
|
+
/>
|
|
87
|
+
</Pressable>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type ScheduleDayHeaderLayoutProps = {
|
|
5
|
+
width: number;
|
|
6
|
+
/** Day heading supplied by dayHeaderComponent, aligned with its column. */
|
|
7
|
+
headerZone: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function ScheduleDayHeaderLayout({ width, headerZone }: ScheduleDayHeaderLayoutProps) {
|
|
11
|
+
return <View style={{ width, paddingBottom: 18 }}>{headerZone}</View>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type ScheduleDayLayoutProps = {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
/** Stacking level for chrome above every rect; the engine supplies it from the lane's layers. */
|
|
8
|
+
chromeZ: number;
|
|
9
|
+
/** Equal wall-clock hour bands, behind all rects. */
|
|
10
|
+
gridZone: ReactNode;
|
|
11
|
+
/** The day's rects and pressable gaps, at final engine bounds. */
|
|
12
|
+
columnZone: ReactNode;
|
|
13
|
+
/** Noninteractive skipped or repeated region chrome above rects. */
|
|
14
|
+
transitionZone: ReactNode;
|
|
15
|
+
/** Noninteractive current-time line at the projected occurrence. */
|
|
16
|
+
nowLineZone: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function ScheduleDayLayout({
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
chromeZ,
|
|
23
|
+
gridZone,
|
|
24
|
+
columnZone,
|
|
25
|
+
transitionZone,
|
|
26
|
+
nowLineZone,
|
|
27
|
+
}: ScheduleDayLayoutProps) {
|
|
28
|
+
return (
|
|
29
|
+
<View style={{ width, height, overflow: 'hidden' }}>
|
|
30
|
+
{gridZone}
|
|
31
|
+
{columnZone}
|
|
32
|
+
<View pointerEvents="none" style={{ position: 'absolute', width, height, zIndex: chromeZ }}>
|
|
33
|
+
{transitionZone}
|
|
34
|
+
{nowLineZone}
|
|
35
|
+
</View>
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import { Pressable } from 'react-native';
|
|
3
|
+
import { pressPoint } from '../../../primitives/press-point';
|
|
4
|
+
import type { ScheduleColumnInput } from '../../schedule.types';
|
|
5
|
+
|
|
6
|
+
export function ScheduleColumn({
|
|
7
|
+
lane,
|
|
8
|
+
rects,
|
|
9
|
+
gapRects,
|
|
10
|
+
press,
|
|
11
|
+
intervalComponent: IntervalComponent,
|
|
12
|
+
gapComponent: GapComponent,
|
|
13
|
+
highlightSource,
|
|
14
|
+
}: ScheduleColumnInput) {
|
|
15
|
+
return [...lane.layers]
|
|
16
|
+
.sort((a, b) => a.z - b.z)
|
|
17
|
+
.map((layer) => (
|
|
18
|
+
<Fragment key={layer.id}>
|
|
19
|
+
{rects
|
|
20
|
+
.filter((rect) => rect.layerId === layer.id)
|
|
21
|
+
.map((rect) => (
|
|
22
|
+
<Fragment key={`interval-${rect.y}:${rect.height}`}>
|
|
23
|
+
<IntervalComponent
|
|
24
|
+
rect={rect}
|
|
25
|
+
layer={layer}
|
|
26
|
+
lane={lane}
|
|
27
|
+
highlighted={rect.sources.some(
|
|
28
|
+
(source) =>
|
|
29
|
+
source.kind === highlightSource?.kind && source.id === highlightSource.id,
|
|
30
|
+
)}
|
|
31
|
+
/>
|
|
32
|
+
</Fragment>
|
|
33
|
+
))}
|
|
34
|
+
{gapRects
|
|
35
|
+
.filter((rect) => rect.layerId === layer.id)
|
|
36
|
+
.map((rect) => (
|
|
37
|
+
<Pressable
|
|
38
|
+
key={`gap-${rect.y}:${rect.height}`}
|
|
39
|
+
accessibilityLabel={`${lane.label}: removed time`}
|
|
40
|
+
onPress={(input) => {
|
|
41
|
+
input.stopPropagation();
|
|
42
|
+
const point = pressPoint(input);
|
|
43
|
+
press(rect.x + point.x, rect.y + point.y);
|
|
44
|
+
}}
|
|
45
|
+
style={{
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
left: rect.x,
|
|
48
|
+
top: rect.y,
|
|
49
|
+
width: rect.width,
|
|
50
|
+
height: rect.height,
|
|
51
|
+
zIndex: rect.z,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<GapComponent rect={rect} layer={layer} lane={lane} />
|
|
55
|
+
</Pressable>
|
|
56
|
+
))}
|
|
57
|
+
</Fragment>
|
|
58
|
+
));
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
import type { DayColumn } from '../../../../core';
|
|
3
|
+
|
|
4
|
+
export function ScheduleDayHeader({ day }: { day: DayColumn }) {
|
|
5
|
+
const badge =
|
|
6
|
+
day.transitions.length > 0 ? (
|
|
7
|
+
<Text style={{ fontSize: 9, color: '#92400e' }}>Clock change</Text>
|
|
8
|
+
) : null;
|
|
9
|
+
return (
|
|
10
|
+
<View style={{ paddingVertical: 4, alignItems: 'center', minHeight: 62 }}>
|
|
11
|
+
<Text style={{ fontSize: 12, color: '#0f172a' }}>{day.label}</Text>
|
|
12
|
+
<Text style={{ width: '100%', fontSize: 9, color: '#475569', textAlign: 'center' }}>
|
|
13
|
+
{day.localDate}
|
|
14
|
+
</Text>
|
|
15
|
+
{badge}
|
|
16
|
+
</View>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import type { ScheduleHoursInput } from '../../schedule.types';
|
|
3
|
+
|
|
4
|
+
export function ScheduleGrid({ hours, pxPerHour }: ScheduleHoursInput) {
|
|
5
|
+
return (
|
|
6
|
+
<View pointerEvents="none" style={{ position: 'absolute', width: '100%' }}>
|
|
7
|
+
{hours.map((hour) => (
|
|
8
|
+
<View
|
|
9
|
+
key={hour}
|
|
10
|
+
testID="schedule-hour-band"
|
|
11
|
+
style={{
|
|
12
|
+
height: pxPerHour,
|
|
13
|
+
borderTopWidth: 1,
|
|
14
|
+
borderRightWidth: 1,
|
|
15
|
+
borderColor: '#e2e8f0',
|
|
16
|
+
}}
|
|
17
|
+
/>
|
|
18
|
+
))}
|
|
19
|
+
</View>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export function ScheduleNowLine({ y, column }: { y: number; column: number }) {
|
|
4
|
+
return (
|
|
5
|
+
<View
|
|
6
|
+
testID={`schedule-now-${column}`}
|
|
7
|
+
pointerEvents="none"
|
|
8
|
+
style={{ position: 'absolute', top: y, width: '100%', height: 2, backgroundColor: '#dc2626' }}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|