react-native-roster 0.0.0 → 0.0.1
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 +378 -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/index.d.ts +3 -0
- package/dist/src/components/roster/index.d.ts.map +1 -0
- package/dist/src/components/roster/index.js +56 -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 +37 -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 +29 -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 +5 -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/roster.types.d.ts +146 -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 +26 -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 +33 -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 +45 -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 +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +63 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/nativewind/index.d.ts +61 -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/llms.txt +481 -0
- package/package.json +154 -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 +11 -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 +18 -0
- package/src/components/roster/index.tsx +90 -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 +88 -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 +91 -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 +18 -0
- package/src/components/roster/parts/lane-label-column.tsx +31 -0
- package/src/components/roster/roster.types.ts +155 -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 +18 -0
- package/src/components/schedule/days/day.tsx +76 -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 +109 -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 +49 -0
- package/src/nativewind/README.md +14 -0
- package/src/nativewind/index.ts +30 -0
- package/index.js +0 -4
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export function ScheduleSkippedDate({ localDate }: { localDate: string }) {
|
|
4
|
+
return (
|
|
5
|
+
<View testID={`schedule-missing-${localDate}`} style={{ width: 0, zIndex: 1 }}>
|
|
6
|
+
<Text
|
|
7
|
+
accessibilityLabel={`Skipped local date ${localDate}`}
|
|
8
|
+
style={{
|
|
9
|
+
width: 110,
|
|
10
|
+
marginLeft: -55,
|
|
11
|
+
marginTop: 62,
|
|
12
|
+
fontSize: 9,
|
|
13
|
+
color: '#92400e',
|
|
14
|
+
backgroundColor: '#fef3c7',
|
|
15
|
+
textAlign: 'center',
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
Skipped {localDate}
|
|
19
|
+
</Text>
|
|
20
|
+
</View>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
import type { ScheduleTransitionInput } from '../../schedule.types';
|
|
3
|
+
|
|
4
|
+
export function ScheduleTransition({
|
|
5
|
+
transition,
|
|
6
|
+
y,
|
|
7
|
+
dividerY,
|
|
8
|
+
height,
|
|
9
|
+
width,
|
|
10
|
+
}: ScheduleTransitionInput) {
|
|
11
|
+
if (height === 0) return null;
|
|
12
|
+
if (transition.deltaMinutes < 0)
|
|
13
|
+
return (
|
|
14
|
+
<View
|
|
15
|
+
testID="schedule-repeat"
|
|
16
|
+
pointerEvents="none"
|
|
17
|
+
style={{
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
top: dividerY,
|
|
20
|
+
width,
|
|
21
|
+
borderTopWidth: 1,
|
|
22
|
+
borderTopColor: '#92400e',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Text
|
|
26
|
+
style={{
|
|
27
|
+
fontSize: 9,
|
|
28
|
+
color: '#92400e',
|
|
29
|
+
backgroundColor: '#fffbeb',
|
|
30
|
+
alignSelf: 'flex-start',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
again
|
|
34
|
+
</Text>
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
return (
|
|
38
|
+
<View
|
|
39
|
+
testID="schedule-skip"
|
|
40
|
+
pointerEvents="none"
|
|
41
|
+
accessibilityLabel="Skipped time"
|
|
42
|
+
style={{
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
top: y,
|
|
45
|
+
height,
|
|
46
|
+
width,
|
|
47
|
+
overflow: 'hidden',
|
|
48
|
+
backgroundColor: '#fef3c7',
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{Array.from({ length: Math.ceil((width + height) / 8) }, (_, index) => {
|
|
52
|
+
const left = index * 8 - height;
|
|
53
|
+
return (
|
|
54
|
+
<View
|
|
55
|
+
key={left}
|
|
56
|
+
style={{
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
left,
|
|
59
|
+
top: -height,
|
|
60
|
+
width: 1,
|
|
61
|
+
height: height * 3,
|
|
62
|
+
backgroundColor: '#d97706',
|
|
63
|
+
transform: [{ rotate: '45deg' }],
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { RosterGap } from '../layers/parts/gap';
|
|
4
|
+
import { RosterInterval } from '../layers/parts/interval';
|
|
5
|
+
import { regionStyle } from '../primitives/region-style';
|
|
6
|
+
import { ScheduleDay } from './days/day';
|
|
7
|
+
import { ScheduleDayHeaderLayout } from './days/header-layout';
|
|
8
|
+
import { ScheduleColumn } from './days/parts/column';
|
|
9
|
+
import { ScheduleDayHeader } from './days/parts/day-header';
|
|
10
|
+
import { ScheduleGrid } from './days/parts/grid';
|
|
11
|
+
import { ScheduleNowLine } from './days/parts/now-line';
|
|
12
|
+
import { ScheduleSkippedDate } from './days/parts/skipped-date';
|
|
13
|
+
import { ScheduleTransition } from './days/parts/transition';
|
|
14
|
+
import { ScheduleLayout } from './layout';
|
|
15
|
+
import { ScheduleGutter } from './parts/gutter';
|
|
16
|
+
import { ScheduleIncomplete } from './parts/incomplete';
|
|
17
|
+
import type { ScheduleProps } from './schedule.types';
|
|
18
|
+
import { useSchedule } from './use-schedule';
|
|
19
|
+
import { ScheduleWidth, useScheduleViewport } from './use-schedule-viewport';
|
|
20
|
+
import { headerDates, nowPosition } from './utils/days';
|
|
21
|
+
|
|
22
|
+
const hours = Array.from({ length: 24 }, (_, hour) => hour);
|
|
23
|
+
|
|
24
|
+
export function Schedule(props: ScheduleProps) {
|
|
25
|
+
const { width, onLayout } = useScheduleViewport();
|
|
26
|
+
// Wait for measurement so the first mounted projection is the actual viewport key.
|
|
27
|
+
const content =
|
|
28
|
+
width === null ? null : (
|
|
29
|
+
<ScheduleWidth.Provider value={width}>
|
|
30
|
+
<ScheduleContent {...props} />
|
|
31
|
+
</ScheduleWidth.Provider>
|
|
32
|
+
);
|
|
33
|
+
return (
|
|
34
|
+
<View
|
|
35
|
+
onLayout={onLayout}
|
|
36
|
+
style={regionStyle(
|
|
37
|
+
{ flex: 1, minHeight: 0, minWidth: 0, overflow: 'hidden' },
|
|
38
|
+
{ backgroundColor: '#fff' },
|
|
39
|
+
props.style,
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{content}
|
|
43
|
+
</View>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ScheduleContent(props: ScheduleProps) {
|
|
48
|
+
const { days, projection, geometry, now, press } = useSchedule(props);
|
|
49
|
+
const {
|
|
50
|
+
lane,
|
|
51
|
+
windowSpec,
|
|
52
|
+
highlightSource,
|
|
53
|
+
gutterZone = ScheduleGutter,
|
|
54
|
+
dayHeaderZone = ScheduleDayHeader,
|
|
55
|
+
skippedDateZone = ScheduleSkippedDate,
|
|
56
|
+
columnZone = ScheduleColumn,
|
|
57
|
+
gridZone = ScheduleGrid,
|
|
58
|
+
transitionZone = ScheduleTransition,
|
|
59
|
+
nowLineZone = ScheduleNowLine,
|
|
60
|
+
intervalZone = RosterInterval,
|
|
61
|
+
gapZone = RosterGap,
|
|
62
|
+
incompleteZone = ScheduleIncomplete,
|
|
63
|
+
incompleteLabel = 'Availability may be incomplete',
|
|
64
|
+
} = props;
|
|
65
|
+
const position = nowPosition(projection, now);
|
|
66
|
+
const incomplete =
|
|
67
|
+
lane.complete === false ? incompleteZone({ lane, label: incompleteLabel }) : null;
|
|
68
|
+
return (
|
|
69
|
+
<ScheduleLayout
|
|
70
|
+
headerStyle={props.headerStyle}
|
|
71
|
+
gutterStyle={props.gutterStyle}
|
|
72
|
+
daysStyle={props.daysStyle}
|
|
73
|
+
incompleteZone={incomplete}
|
|
74
|
+
gutterZone={gutterZone({ hours, pxPerHour: projection.pxPerHour })}
|
|
75
|
+
dayHeaderZone={headerDates(windowSpec, days).map(({ localDate, day }) => {
|
|
76
|
+
if (!day) return <Fragment key={localDate}>{skippedDateZone({ localDate })}</Fragment>;
|
|
77
|
+
return (
|
|
78
|
+
<ScheduleDayHeaderLayout
|
|
79
|
+
key={localDate}
|
|
80
|
+
width={projection.columnWidth}
|
|
81
|
+
headerZone={dayHeaderZone({ day })}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
daysZone={days.map((day, column) => {
|
|
86
|
+
const nowLine = position?.column === column ? nowLineZone(position) : null;
|
|
87
|
+
return (
|
|
88
|
+
<ScheduleDay
|
|
89
|
+
key={day.localDate}
|
|
90
|
+
day={day}
|
|
91
|
+
column={column}
|
|
92
|
+
lane={lane}
|
|
93
|
+
projection={projection}
|
|
94
|
+
geometry={geometry}
|
|
95
|
+
highlightSource={highlightSource}
|
|
96
|
+
hours={hours}
|
|
97
|
+
press={(x, y) => press(column, x, y)}
|
|
98
|
+
gridZone={gridZone}
|
|
99
|
+
columnZone={columnZone}
|
|
100
|
+
transitionZone={transitionZone}
|
|
101
|
+
intervalZone={intervalZone}
|
|
102
|
+
gapZone={gapZone}
|
|
103
|
+
nowLineZone={nowLine}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
})}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { ScrollView, View } from 'react-native';
|
|
3
|
+
import { regionStyle } from '../primitives/region-style';
|
|
4
|
+
import type { ScheduleStyleProps } from './schedule.types';
|
|
5
|
+
|
|
6
|
+
type ScheduleLayoutProps = Pick<ScheduleStyleProps, 'headerStyle' | 'gutterStyle' | 'daysStyle'> & {
|
|
7
|
+
/** Frozen day headings, including markers for wholly skipped local dates. */
|
|
8
|
+
dayHeaderZone: ReactNode;
|
|
9
|
+
/** Hour labels in a 48 px left gutter; shares the body's vertical scroll. */
|
|
10
|
+
gutterZone: ReactNode;
|
|
11
|
+
/** Day columns, each containing its grid, rects, transitions, and current-time line. */
|
|
12
|
+
daysZone: ReactNode;
|
|
13
|
+
/** Completeness notice in reserved empty space above the grid. */
|
|
14
|
+
incompleteZone: ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function ScheduleLayout({
|
|
18
|
+
dayHeaderZone,
|
|
19
|
+
gutterZone,
|
|
20
|
+
daysZone,
|
|
21
|
+
incompleteZone,
|
|
22
|
+
headerStyle,
|
|
23
|
+
gutterStyle,
|
|
24
|
+
daysStyle,
|
|
25
|
+
}: ScheduleLayoutProps) {
|
|
26
|
+
return (
|
|
27
|
+
<View style={{ flex: 1, minHeight: 0 }}>
|
|
28
|
+
<View style={regionStyle({ marginLeft: 48, flexDirection: 'row' }, {}, headerStyle)}>
|
|
29
|
+
{dayHeaderZone}
|
|
30
|
+
</View>
|
|
31
|
+
<View>{incompleteZone}</View>
|
|
32
|
+
<ScrollView testID="schedule-vertical-scroll" horizontal={false} style={{ flex: 1 }}>
|
|
33
|
+
<View style={{ flexDirection: 'row' }}>
|
|
34
|
+
<View style={regionStyle({ width: 48 }, {}, gutterStyle)}>{gutterZone}</View>
|
|
35
|
+
<View style={regionStyle({ flex: 1, minWidth: 0, flexDirection: 'row' }, {}, daysStyle)}>
|
|
36
|
+
{daysZone}
|
|
37
|
+
</View>
|
|
38
|
+
</View>
|
|
39
|
+
</ScrollView>
|
|
40
|
+
</View>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Text, View } from 'react-native';
|
|
2
|
+
import type { ScheduleHoursInput } from '../schedule.types';
|
|
3
|
+
|
|
4
|
+
export function ScheduleGutter({ hours, pxPerHour }: ScheduleHoursInput) {
|
|
5
|
+
return (
|
|
6
|
+
<View>
|
|
7
|
+
{hours.map((hour) => (
|
|
8
|
+
<View key={hour} style={{ height: pxPerHour }}>
|
|
9
|
+
<Text style={{ fontSize: 10, color: '#64748b', padding: 3 }}>
|
|
10
|
+
{String(hour).padStart(2, '0')}:00
|
|
11
|
+
</Text>
|
|
12
|
+
</View>
|
|
13
|
+
))}
|
|
14
|
+
</View>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Text } from 'react-native';
|
|
2
|
+
import type { Lane } from '../../../core';
|
|
3
|
+
|
|
4
|
+
export function ScheduleIncomplete({ lane, label }: { lane: Lane; label: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<Text
|
|
7
|
+
accessibilityLabel={`${lane.label}: ${label}`}
|
|
8
|
+
style={{ padding: 8, color: '#9a3412', fontSize: 12 }}
|
|
9
|
+
>
|
|
10
|
+
{label}
|
|
11
|
+
</Text>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type {
|
|
4
|
+
DayColumn,
|
|
5
|
+
Lane,
|
|
6
|
+
LaneGeometry,
|
|
7
|
+
Projection,
|
|
8
|
+
Rect,
|
|
9
|
+
Source,
|
|
10
|
+
Transition,
|
|
11
|
+
Window,
|
|
12
|
+
WindowSpec,
|
|
13
|
+
} from '../../core';
|
|
14
|
+
import type { GapInput, IntervalInput } from '../layers/layers.types';
|
|
15
|
+
|
|
16
|
+
export type ScheduleWindowSpec = WindowSpec & { span: 'day' | 'week' };
|
|
17
|
+
export type ScheduleProjection = Extract<Projection, { orientation: 'columns' }>;
|
|
18
|
+
export type ScheduleInput = {
|
|
19
|
+
lane: Lane;
|
|
20
|
+
windowSpec: ScheduleWindowSpec;
|
|
21
|
+
minuteStep?: number;
|
|
22
|
+
/** Positive finite pixels per wall-clock hour; defaults to 48. */
|
|
23
|
+
pxPerHour?: number;
|
|
24
|
+
highlightSource?: Source;
|
|
25
|
+
onNavigate?: (next: WindowSpec) => void;
|
|
26
|
+
onIntervalPress?: (rect: Rect, lane: Lane) => void;
|
|
27
|
+
onGapPress?: (rect: Rect, lane: Lane) => void;
|
|
28
|
+
onCellPress?: (lane: Lane, time: number) => void;
|
|
29
|
+
};
|
|
30
|
+
export type ScheduleModel = {
|
|
31
|
+
window: Window;
|
|
32
|
+
days: DayColumn[];
|
|
33
|
+
projection: ScheduleProjection;
|
|
34
|
+
geometry: LaneGeometry;
|
|
35
|
+
now: number | null;
|
|
36
|
+
press: (columnIndex: number, x: number, y: number) => void;
|
|
37
|
+
status: 'ready';
|
|
38
|
+
};
|
|
39
|
+
export type ScheduleTransitionInput = {
|
|
40
|
+
day: DayColumn;
|
|
41
|
+
transition: Transition;
|
|
42
|
+
/** Projected transition instant, separating repeated occurrences. */
|
|
43
|
+
dividerY: number;
|
|
44
|
+
y: number;
|
|
45
|
+
height: number;
|
|
46
|
+
width: number;
|
|
47
|
+
};
|
|
48
|
+
export type ScheduleColumnInput = {
|
|
49
|
+
day: DayColumn;
|
|
50
|
+
rects: Rect[];
|
|
51
|
+
gapRects: Rect[];
|
|
52
|
+
lane: Lane;
|
|
53
|
+
highlightSource?: Source;
|
|
54
|
+
press: (x: number, y: number) => void;
|
|
55
|
+
/** Covered rect filler shared with Roster; final bounds and stacking belong to the rect. */
|
|
56
|
+
intervalZone: (input: IntervalInput) => ReactNode;
|
|
57
|
+
/** Removed rect content shared with Roster; the column supplies its pressable bounds. */
|
|
58
|
+
gapZone: (input: GapInput) => ReactNode;
|
|
59
|
+
};
|
|
60
|
+
export type ScheduleHoursInput = { hours: number[]; pxPerHour: number };
|
|
61
|
+
/**
|
|
62
|
+
* Chrome style props and their NativeWind class twins. Class props resolve only
|
|
63
|
+
* after `react-native-roster/nativewind` registers the component; without that
|
|
64
|
+
* entry they are ignored.
|
|
65
|
+
*/
|
|
66
|
+
export type ScheduleStyleProps = {
|
|
67
|
+
/** Outer measured container. */
|
|
68
|
+
style?: StyleProp<ViewStyle>;
|
|
69
|
+
className?: string;
|
|
70
|
+
/** Frozen day heading row, indented past the 48 px gutter. */
|
|
71
|
+
headerStyle?: StyleProp<ViewStyle>;
|
|
72
|
+
headerClassName?: string;
|
|
73
|
+
/** Scrolling 48 px hour gutter. */
|
|
74
|
+
gutterStyle?: StyleProp<ViewStyle>;
|
|
75
|
+
gutterClassName?: string;
|
|
76
|
+
/** Scrolling day column container beside the gutter. */
|
|
77
|
+
daysStyle?: StyleProp<ViewStyle>;
|
|
78
|
+
daysClassName?: string;
|
|
79
|
+
};
|
|
80
|
+
export type ScheduleProps = ScheduleInput &
|
|
81
|
+
ScheduleStyleProps & {
|
|
82
|
+
incompleteLabel?: string;
|
|
83
|
+
/** Frozen left hour labels; ScheduleGutter receives hours 0 through 23 and their scale. */
|
|
84
|
+
gutterZone?: (input: ScheduleHoursInput) => ReactNode;
|
|
85
|
+
/** Hour bands behind each day's rects; ScheduleGrid draws 24 bordered bands. */
|
|
86
|
+
gridZone?: (input: ScheduleHoursInput) => ReactNode;
|
|
87
|
+
/** Frozen day heading; ScheduleDayHeader shows weekday, localDate, and a transition badge. */
|
|
88
|
+
dayHeaderZone?: (input: { day: DayColumn }) => ReactNode;
|
|
89
|
+
/** Wholly skipped local date marker; ScheduleSkippedDate labels its zero-width header gap. */
|
|
90
|
+
skippedDateZone?: (input: { localDate: string }) => ReactNode;
|
|
91
|
+
/** The day's covered and removed rects; ScheduleColumn draws final bounds in layer order. */
|
|
92
|
+
columnZone?: (input: ScheduleColumnInput) => ReactNode;
|
|
93
|
+
/** Transition chrome only; ScheduleTransition hatches skips and divides repeats with again. */
|
|
94
|
+
transitionZone?: (input: ScheduleTransitionInput) => ReactNode;
|
|
95
|
+
/** Current day's line at y; ScheduleNowLine spans the containing column. */
|
|
96
|
+
nowLineZone?: (input: { y: number; column: number }) => ReactNode;
|
|
97
|
+
/** Covered rect filler shared with Roster; position using rect bounds and use pointerEvents="none". */
|
|
98
|
+
intervalZone?: (input: IntervalInput) => ReactNode;
|
|
99
|
+
/** Removed rect content shared with Roster; ScheduleColumn supplies the invisible pressable. */
|
|
100
|
+
gapZone?: (input: GapInput) => ReactNode;
|
|
101
|
+
/** Completeness notice in reserved empty space above the day grid; receives the lane and localized label. */
|
|
102
|
+
incompleteZone?: (input: { lane: Lane; label: string }) => ReactNode;
|
|
103
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useState } from 'react';
|
|
2
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// Standalone hooks use a 280 px grid; the chassis supplies its measured grid width.
|
|
5
|
+
export const ScheduleWidth = createContext(280);
|
|
6
|
+
|
|
7
|
+
export function useScheduleViewport() {
|
|
8
|
+
const [width, setWidth] = useState<number | null>(null);
|
|
9
|
+
function onLayout({ nativeEvent: { layout } }: LayoutChangeEvent) {
|
|
10
|
+
setWidth(Math.max(0, layout.width - 48));
|
|
11
|
+
}
|
|
12
|
+
return { width, onLayout };
|
|
13
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { dayColumnsFor, layoutLane, snapToStep, timeAtY, windowFor } from '../../core';
|
|
3
|
+
import { hitTest } from '../../core/hit-test';
|
|
4
|
+
import type { ScheduleInput, ScheduleModel, ScheduleProjection } from './schedule.types';
|
|
5
|
+
import { ScheduleWidth } from './use-schedule-viewport';
|
|
6
|
+
|
|
7
|
+
export function useSchedule(input: ScheduleInput): ScheduleModel {
|
|
8
|
+
const { lane, windowSpec, minuteStep = 60, pxPerHour = 48 } = input;
|
|
9
|
+
const width = useContext(ScheduleWidth);
|
|
10
|
+
const [clock, setClock] = useState(Date.now);
|
|
11
|
+
// The current-time indicator follows the wall clock for the mounted surface's lifetime.
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const timer = setInterval(() => setClock(Date.now()), 60_000);
|
|
14
|
+
return () => clearInterval(timer);
|
|
15
|
+
}, []);
|
|
16
|
+
if (!Number.isInteger(minuteStep) || minuteStep <= 0 || 60 % minuteStep !== 0)
|
|
17
|
+
throw new RangeError('minuteStep must be a positive divisor of 60');
|
|
18
|
+
if (!Number.isFinite(pxPerHour) || pxPerHour <= 0)
|
|
19
|
+
throw new RangeError('pxPerHour must be a positive finite number');
|
|
20
|
+
const window = windowFor(windowSpec);
|
|
21
|
+
const days = dayColumnsFor(window, windowSpec.timezone);
|
|
22
|
+
const projection: ScheduleProjection = {
|
|
23
|
+
orientation: 'columns',
|
|
24
|
+
viewTimezone: windowSpec.timezone,
|
|
25
|
+
pxPerHour,
|
|
26
|
+
columnWidth: width / Math.max(1, days.length),
|
|
27
|
+
days,
|
|
28
|
+
};
|
|
29
|
+
const geometry = layoutLane(lane, window, projection);
|
|
30
|
+
const currentPress = { input, window, projection, geometry };
|
|
31
|
+
const pressInput = useRef(currentPress);
|
|
32
|
+
pressInput.current = currentPress;
|
|
33
|
+
const [press] = useState(() => {
|
|
34
|
+
return function press(columnIndex: number, x: number, y: number): void {
|
|
35
|
+
const { input, window, projection, geometry } = pressInput.current;
|
|
36
|
+
const { lane, minuteStep = 60, onIntervalPress, onGapPress, onCellPress } = input;
|
|
37
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || x >= projection.columnWidth)
|
|
38
|
+
return;
|
|
39
|
+
const absolute = timeAtY(projection, columnIndex, y);
|
|
40
|
+
if (absolute === null) return;
|
|
41
|
+
const time = Math.max(
|
|
42
|
+
window.start,
|
|
43
|
+
snapToStep(absolute, minuteStep, projection.viewTimezone),
|
|
44
|
+
);
|
|
45
|
+
const hit = hitTest(
|
|
46
|
+
lane,
|
|
47
|
+
{
|
|
48
|
+
...geometry,
|
|
49
|
+
rects: geometry.rects.filter((rect) => rect.column === columnIndex),
|
|
50
|
+
gapRects: geometry.gapRects.filter((rect) => rect.column === columnIndex),
|
|
51
|
+
},
|
|
52
|
+
x,
|
|
53
|
+
y,
|
|
54
|
+
);
|
|
55
|
+
if (hit?.kind === 'interval') {
|
|
56
|
+
onIntervalPress?.(hit.rect, lane);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (hit?.kind === 'gap') {
|
|
60
|
+
onGapPress?.(hit.rect, lane);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (time < window.end) onCellPress?.(lane, time);
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
window,
|
|
68
|
+
days,
|
|
69
|
+
projection,
|
|
70
|
+
geometry,
|
|
71
|
+
now: clock >= window.start && clock < window.end ? clock : null,
|
|
72
|
+
press,
|
|
73
|
+
status: 'ready',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { DayColumn, Transition } from '../../../core';
|
|
2
|
+
import { scalePieces } from '../../../core/scale';
|
|
3
|
+
import { dateEpoch, dateString, dayMilliseconds, wallTime } from '../../../core/zone';
|
|
4
|
+
import type { ScheduleProjection, ScheduleWindowSpec } from '../schedule.types';
|
|
5
|
+
|
|
6
|
+
export function transitionBounds(
|
|
7
|
+
day: DayColumn,
|
|
8
|
+
transition: Transition,
|
|
9
|
+
projection: ScheduleProjection,
|
|
10
|
+
) {
|
|
11
|
+
const minute =
|
|
12
|
+
(wallTime(transition.at, projection.viewTimezone) - dateEpoch(day.localDate)) / 60_000;
|
|
13
|
+
const delta = transition.deltaMinutes;
|
|
14
|
+
const start = Math.max(0, delta > 0 ? minute - delta : minute);
|
|
15
|
+
const end = Math.min(1440, delta < 0 ? minute - delta : minute);
|
|
16
|
+
const pxPerMinute = projection.pxPerHour / 60;
|
|
17
|
+
const y = start * pxPerMinute;
|
|
18
|
+
return {
|
|
19
|
+
y,
|
|
20
|
+
dividerY: nowPosition(projection, transition.at)?.y ?? y,
|
|
21
|
+
height: Math.max(0, end - start) * pxPerMinute,
|
|
22
|
+
width: projection.columnWidth,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function nowPosition(projection: ScheduleProjection, now: number | null) {
|
|
27
|
+
if (now !== null) {
|
|
28
|
+
for (const [column, day] of projection.days.entries()) {
|
|
29
|
+
for (const piece of scalePieces(day, projection.viewTimezone)) {
|
|
30
|
+
if (now >= piece.start && now < piece.end)
|
|
31
|
+
return {
|
|
32
|
+
column,
|
|
33
|
+
y:
|
|
34
|
+
((piece.minute + ((now - piece.start) / 60_000) * piece.scale) *
|
|
35
|
+
projection.pxPerHour) /
|
|
36
|
+
60,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function headerDates(spec: ScheduleWindowSpec, days: DayColumn[]) {
|
|
45
|
+
let start = dateEpoch(spec.anchorDate);
|
|
46
|
+
if (spec.span === 'week') {
|
|
47
|
+
const weekday = (new Date(start).getUTCDay() + 6) % 7;
|
|
48
|
+
start -= ((weekday - (spec.wkst ?? 0) + 7) % 7) * dayMilliseconds;
|
|
49
|
+
}
|
|
50
|
+
return Array.from({ length: spec.span === 'week' ? 7 : 1 }, (_, index) => {
|
|
51
|
+
const localDate = dateString(start + index * dayMilliseconds);
|
|
52
|
+
return { localDate, day: days.find((day) => day.localDate === localDate) };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# core
|
|
2
|
+
|
|
3
|
+
Pure types and geometry. Imports only standard JavaScript and `Intl`; no React,
|
|
4
|
+
no React Native, no dependencies.
|
|
5
|
+
|
|
6
|
+
- Public subpath: `react-native-roster/core`; the root entry re-exports it
|
|
7
|
+
- Authoritative types: `types.ts` (`Lane`, `Layer`, `Interval`, `Gap`,
|
|
8
|
+
`Source`, `Window`, `Projection`, `Rect`, `Coverage`, `LaneGeometry`)
|
|
9
|
+
- Barrel: `index.ts` only
|
|
10
|
+
|
|
11
|
+
| File | Role |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `axis.ts` | window specs, spans, and navigation (`windowFor`, `next`, `prev`, `today`) |
|
|
14
|
+
| `layout.ts` | rect geometry for one lane in either projection |
|
|
15
|
+
| `coverage.ts` | covered and removed minutes per lane |
|
|
16
|
+
| `spans.ts` | the provenance sweep: exact sources per merged span |
|
|
17
|
+
| `columns.ts` | day columns, transitions, and skipped dates for the columns projection |
|
|
18
|
+
| `zone.ts` | Intl-only timezone math (offsets, first instants, transitions) |
|
|
19
|
+
| `scale.ts`, `snap.ts` | pixel and time conversion, pointer snapping |
|
|
20
|
+
| `hit-test.ts` | the geometry walk both hooks use to resolve a press |
|
|
21
|
+
| `order.ts`, `flag.ts` | lane comparators and lane flags |
|
|
22
|
+
| `cache.ts`, `hash.ts` | layout and coverage caches keyed by lane content |
|
|
23
|
+
|
|
24
|
+
Reference: [design](../../docs/design.md), [timezones](../../docs/timezones.md),
|
|
25
|
+
[caches](../../docs/caches.md). Tests: `test/core`.
|
package/src/core/axis.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Weekday, Window } from './types';
|
|
2
|
+
import { dateEpoch, dateString, dayMilliseconds, localDateAt, startOfDate } from './zone';
|
|
3
|
+
|
|
4
|
+
export type Span = 'day' | 'week' | 'month';
|
|
5
|
+
/** The anchor is a local calendar date in `timezone`, not an instant, so a zone change keeps the same local week. */
|
|
6
|
+
export type WindowSpec =
|
|
7
|
+
| { span: Span; anchorDate: string; wkst?: Weekday; timezone: string }
|
|
8
|
+
| { span: 'custom'; window: Window; timezone: string };
|
|
9
|
+
|
|
10
|
+
export function windowFor(spec: WindowSpec): Window {
|
|
11
|
+
if (spec.span === 'custom') return spec.window;
|
|
12
|
+
const date = new Date(dateEpoch(spec.anchorDate));
|
|
13
|
+
if (spec.span === 'week') {
|
|
14
|
+
const weekday = (date.getUTCDay() + 6) % 7;
|
|
15
|
+
date.setUTCDate(date.getUTCDate() - ((weekday - (spec.wkst ?? 0) + 7) % 7));
|
|
16
|
+
}
|
|
17
|
+
if (spec.span === 'month') date.setUTCDate(1);
|
|
18
|
+
const start = startOfDate(dateString(date.getTime()), spec.timezone);
|
|
19
|
+
if (spec.span === 'month') date.setUTCMonth(date.getUTCMonth() + 1);
|
|
20
|
+
else date.setUTCDate(date.getUTCDate() + (spec.span === 'week' ? 7 : 1));
|
|
21
|
+
return { start, end: startOfDate(dateString(date.getTime()), spec.timezone) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function prev(spec: WindowSpec): WindowSpec {
|
|
25
|
+
return move(spec, -1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function next(spec: WindowSpec): WindowSpec {
|
|
29
|
+
return move(spec, 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function today(spec: WindowSpec): WindowSpec {
|
|
33
|
+
const anchorDate = localDateAt(Date.now(), spec.timezone);
|
|
34
|
+
if (spec.span !== 'custom') return { ...spec, anchorDate };
|
|
35
|
+
const start = startOfDate(anchorDate, spec.timezone);
|
|
36
|
+
return { ...spec, window: { start, end: start + spec.window.end - spec.window.start } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function move(spec: WindowSpec, direction: number): WindowSpec {
|
|
40
|
+
if (spec.span === 'custom') {
|
|
41
|
+
const delta = direction * (spec.window.end - spec.window.start);
|
|
42
|
+
return { ...spec, window: { start: spec.window.start + delta, end: spec.window.end + delta } };
|
|
43
|
+
}
|
|
44
|
+
const time = dateEpoch(spec.anchorDate);
|
|
45
|
+
if (spec.span !== 'month') {
|
|
46
|
+
return {
|
|
47
|
+
...spec,
|
|
48
|
+
anchorDate: dateString(time + direction * (spec.span === 'week' ? 7 : 1) * dayMilliseconds),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const date = new Date(time);
|
|
52
|
+
const day = date.getUTCDate();
|
|
53
|
+
date.setUTCDate(1);
|
|
54
|
+
date.setUTCMonth(date.getUTCMonth() + direction + 1);
|
|
55
|
+
date.setUTCDate(0);
|
|
56
|
+
date.setUTCDate(Math.min(day, date.getUTCDate()));
|
|
57
|
+
return { ...spec, anchorDate: dateString(date.getTime()) };
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Coverage, LaneFlag, LaneGeometry, Rect } from './types';
|
|
2
|
+
|
|
3
|
+
export const layoutCache = new Map<
|
|
4
|
+
string,
|
|
5
|
+
{
|
|
6
|
+
rects: Rect[];
|
|
7
|
+
gapRects: Rect[];
|
|
8
|
+
assemblies: WeakMap<Coverage, Map<LaneFlag, LaneGeometry>>;
|
|
9
|
+
}
|
|
10
|
+
>();
|
|
11
|
+
export const coverageCache = new Map<string, Coverage>();
|
|
12
|
+
export const counts = {
|
|
13
|
+
layout: { runs: 0, cacheHits: 0 },
|
|
14
|
+
coverage: { runs: 0, cacheHits: 0 },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function layoutStats(): { runs: number; cacheHits: number } {
|
|
18
|
+
return { ...counts.layout };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function coverageStats(): { runs: number; cacheHits: number } {
|
|
22
|
+
return { ...counts.coverage };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resetStats(): void {
|
|
26
|
+
counts.layout.runs = 0;
|
|
27
|
+
counts.layout.cacheHits = 0;
|
|
28
|
+
counts.coverage.runs = 0;
|
|
29
|
+
counts.coverage.cacheHits = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearLayoutCache(): void {
|
|
33
|
+
layoutCache.clear();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function clearCoverageCache(): void {
|
|
37
|
+
coverageCache.clear();
|
|
38
|
+
}
|