Publii: update content
This commit is contained in:
parent
01b5bd33ea
commit
081f8ec14b
935 changed files with 87637 additions and 39 deletions
4
404.html
4
404.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
14
feed.json
14
feed.json
|
@ -10,6 +10,20 @@
|
|||
"name": "CZ 🇨🇿"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "https://jsem.nudista.online/pixelfedcz/",
|
||||
"url": "https://jsem.nudista.online/pixelfedcz/",
|
||||
"title": "Pixelfed.cz",
|
||||
"summary": "<p>Registrace dnes 5. března 2024 ve 20:59...</p>\n",
|
||||
"content_html": "<p>Registrace dnes 5. března 2024 ve 20:59...</p>\n\n<p> </p>",
|
||||
"author": {
|
||||
"name": "CZ 🇨🇿"
|
||||
},
|
||||
"tags": [
|
||||
],
|
||||
"date_published": "2024-03-05T22:13:11+01:00",
|
||||
"date_modified": "2024-03-05T22:13:11+01:00"
|
||||
},
|
||||
{
|
||||
"id": "https://jsem.nudista.online/projekt/",
|
||||
"url": "https://jsem.nudista.online/projekt/",
|
||||
|
|
25
feed.xml
25
feed.xml
|
@ -3,12 +3,35 @@
|
|||
<title>NoLogWeb</title>
|
||||
<link href="https://jsem.nudista.online/feed.xml" rel="self" />
|
||||
<link href="https://jsem.nudista.online" />
|
||||
<updated>2024-03-02T15:25:44+01:00</updated>
|
||||
<updated>2024-03-05T22:13:11+01:00</updated>
|
||||
<author>
|
||||
<name>CZ 🇨🇿</name>
|
||||
</author>
|
||||
<id>https://jsem.nudista.online</id>
|
||||
|
||||
<entry>
|
||||
<title>Pixelfed.cz</title>
|
||||
<author>
|
||||
<name>CZ 🇨🇿</name>
|
||||
</author>
|
||||
<link href="https://jsem.nudista.online/pixelfedcz/"/>
|
||||
<id>https://jsem.nudista.online/pixelfedcz/</id>
|
||||
|
||||
<updated>2024-03-05T22:13:11+01:00</updated>
|
||||
<summary>
|
||||
<![CDATA[
|
||||
<p>Registrace dnes 5. března 2024 ve 20:59...</p>
|
||||
|
||||
]]>
|
||||
</summary>
|
||||
<content type="html">
|
||||
<![CDATA[
|
||||
<p>Registrace dnes 5. března 2024 ve 20:59...</p>
|
||||
|
||||
<p> </p>
|
||||
]]>
|
||||
</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Projekt</title>
|
||||
<author>
|
||||
|
|
12
fullcalendar-main/.editorconfig
Normal file
12
fullcalendar-main/.editorconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
quote_type = single
|
49
fullcalendar-main/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
49
fullcalendar-main/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
name: Bug Report
|
||||
description: Report something that doesn't work correctly
|
||||
body:
|
||||
- type: input
|
||||
id: reduced-test-case
|
||||
attributes:
|
||||
label: Reduced Test Case
|
||||
description: >
|
||||
A [reduced test case](https://css-tricks.com/reduced-test-cases/) is required.
|
||||
A [debugging template](https://fullcalendar.io/reduced-test-cases) will help you get started.
|
||||
placeholder: URL of reduced test case
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: reduced-test-case-confirmation
|
||||
attributes:
|
||||
label: >
|
||||
Do you understand that if a reduced test case is not provided,
|
||||
we will intentionally delay triaging of your ticket?
|
||||
options:
|
||||
- label: I understand
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: connector
|
||||
attributes:
|
||||
label: Which connector are you using (React/Angular/etc)?
|
||||
options:
|
||||
- No connector (vanilla JS)
|
||||
- React
|
||||
- Angular
|
||||
- Vue
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: >
|
||||
Describe how to recreate the bug.
|
||||
What do you expect to happen?
|
||||
What happens instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
id: screenshot
|
||||
attributes:
|
||||
value: >
|
||||
**Screenshot:**
|
||||
If the bug is visual, drag a screenshot into the description above.
|
5
fullcalendar-main/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
fullcalendar-main/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Getting Help
|
||||
url: https://stackoverflow.com/questions/tagged/fullcalendar
|
||||
about: Stuck on a tough problem? Visit the StackOverflow fullcalendar tags
|
37
fullcalendar-main/.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
37
fullcalendar-main/.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Feature Request
|
||||
description: Suggest an idea you want implemented
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: confirmations
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I've already searched through [existing tickets](https://github.com/fullcalendar/fullcalendar/issues)
|
||||
required: true
|
||||
- label: Other people will find this feature useful
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: connector
|
||||
attributes:
|
||||
label: Is this feature for a specific connector (React/Angular/etc)?
|
||||
options:
|
||||
- No connector in particular
|
||||
- React
|
||||
- Angular
|
||||
- Vue
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: Please describe what this feature will do.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
id: mockup
|
||||
attributes:
|
||||
value: >
|
||||
**Visual Mockup:**
|
||||
If you are requesting a new UI, drag some sort of mockup or screenshot into the area above.
|
49
fullcalendar-main/.github/workflows/ci.yml
vendored
Normal file
49
fullcalendar-main/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
TZ: "America/New_York"
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8.6.3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.13.0
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Setup Turbo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/.cache/turbo
|
||||
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: turbo-${{ github.job }}-${{ github.ref_name }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
# - name: Test
|
||||
# run: pnpm run test
|
11
fullcalendar-main/.gitignore
vendored
Normal file
11
fullcalendar-main/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
# Package manager
|
||||
node_modules
|
||||
|
||||
# Generated
|
||||
tsconfig.json
|
||||
tsconfig.tsbuildinfo
|
||||
dist
|
||||
|
||||
# Monorepo
|
||||
.turbo
|
7
fullcalendar-main/.npmrc
Normal file
7
fullcalendar-main/.npmrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
engine-strict = true
|
||||
use-node-version = 18.13.0
|
||||
prefer-workspace-packages = true
|
||||
|
||||
# disable so that filtered install can be used. bug:
|
||||
# https://github.com/pnpm/pnpm/issues/6300
|
||||
dedupe-peer-dependents = false
|
5
fullcalendar-main/.vscode/extensions.json
vendored
Normal file
5
fullcalendar-main/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
5
fullcalendar-main/.vscode/settings.json
vendored
Normal file
5
fullcalendar-main/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
2004
fullcalendar-main/CHANGELOG.md
Normal file
2004
fullcalendar-main/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
14
fullcalendar-main/CONTRIBUTING.md
Normal file
14
fullcalendar-main/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
## Contributing Features
|
||||
|
||||
The FullCalendar project welcomes PRs for new features, but because there are so many feature requests, and because every new feature requires refinement and maintenance, each PR will be prioritized against the project's other demands and might take a while to make it to an official release.
|
||||
|
||||
Furthermore, each new feature should be designed as robustly as possible and be useful beyond the immediate usecase it was initially designed for. Feel free to start a ticket discussing the feature's specs before coding.
|
||||
|
||||
## Contributing Bugfixes
|
||||
|
||||
Please link to a bug ticket in the description of your PR. If a ticket doesn't exist, please create one. The ticket must contain a reduced test case.
|
||||
|
||||
## Contributing Locale Data
|
||||
|
||||
Please edit the source files in the `packages/core/locales/` directory.
|
22
fullcalendar-main/LICENSE.md
Normal file
22
fullcalendar-main/LICENSE.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Adam Shaw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
73
fullcalendar-main/README.md
Normal file
73
fullcalendar-main/README.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
# FullCalendar
|
||||
|
||||
Full-sized drag & drop calendar in JavaScript
|
||||
|
||||
- [Project Website](https://fullcalendar.io/)
|
||||
- [Documentation](https://fullcalendar.io/docs)
|
||||
- [Changelog](CHANGELOG.md)
|
||||
- [Support](https://fullcalendar.io/support)
|
||||
- [License](LICENSE.md)
|
||||
- [Roadmap](https://fullcalendar.io/roadmap)
|
||||
|
||||
Connectors:
|
||||
|
||||
- [React](https://github.com/fullcalendar/fullcalendar-react)
|
||||
- [Angular](https://github.com/fullcalendar/fullcalendar-angular)
|
||||
- [Vue 3](https://github.com/fullcalendar/fullcalendar-vue) |
|
||||
[2](https://github.com/fullcalendar/fullcalendar-vue2)
|
||||
|
||||
## Bundle
|
||||
|
||||
The [FullCalendar Standard Bundle](bundle) is easier to install than individual plugins, though filesize will be larger. It works well with a CDN.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the FullCalendar core package and any plugins you plan to use:
|
||||
|
||||
```sh
|
||||
npm install @fullcalendar/core @fullcalendar/interaction @fullcalendar/daygrid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Instantiate a Calendar with plugins and options:
|
||||
|
||||
```js
|
||||
import { Calendar } from '@fullcalendar/core'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new Calendar(calendarEl, {
|
||||
plugins: [
|
||||
interactionPlugin,
|
||||
dayGridPlugin
|
||||
],
|
||||
initialView: 'timeGridWeek',
|
||||
editable: true,
|
||||
events: [
|
||||
{ title: 'Meeting', start: new Date() }
|
||||
]
|
||||
})
|
||||
|
||||
calendar.render()
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
You must install this repo with [PNPM](https://pnpm.io/):
|
||||
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Available scripts (via `pnpm run <script>`):
|
||||
|
||||
- `build` - build production-ready dist files
|
||||
- `dev` - build & watch development dist files
|
||||
- `test` - test headlessly
|
||||
- `test:dev` - test interactively
|
||||
- `lint`
|
||||
- `clean`
|
||||
|
||||
[Info about contributing code »](CONTRIBUTING.md)
|
4
fullcalendar-main/bundle/.eslintrc.cjs
Normal file
4
fullcalendar-main/bundle/.eslintrc.cjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: require.resolve('@fullcalendar-scripts/standard/config/eslint.pkg.browser.cjs'),
|
||||
}
|
58
fullcalendar-main/bundle/README.md
Normal file
58
fullcalendar-main/bundle/README.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
|
||||
# FullCalendar Standard Bundle
|
||||
|
||||
Easily render a full-sized drag & drop calendar with a combination of standard plugins
|
||||
|
||||
This `fullcalendar` package bundles these plugins:
|
||||
|
||||
- [@fullcalendar/core](https://github.com/fullcalendar/fullcalendar/tree/main/packages/core)
|
||||
- [@fullcalendar/interaction](https://github.com/fullcalendar/fullcalendar/tree/main/packages/interaction)
|
||||
- [@fullcalendar/daygrid](https://github.com/fullcalendar/fullcalendar/tree/main/packages/daygrid)
|
||||
- [@fullcalendar/timegrid](https://github.com/fullcalendar/fullcalendar/tree/main/packages/timegrid)
|
||||
- [@fullcalendar/list](https://github.com/fullcalendar/fullcalendar/tree/main/packages/list)
|
||||
- [@fullcalendar/multimonth](https://github.com/fullcalendar/fullcalendar/tree/main/packages/multimonth)
|
||||
|
||||
## Usage with CDN or ZIP archive
|
||||
|
||||
Load the `index.global.min.js` file and use the `FullCalendar` global namespace:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src='https://cdn.jsdelivr.net/npm/fullcalendar/index.global.min.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth'
|
||||
})
|
||||
calendar.render()
|
||||
})
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='calendar'></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Usage with NPM and ES modules
|
||||
|
||||
```sh
|
||||
npm install fullcalendar
|
||||
```
|
||||
|
||||
```js
|
||||
import { Calendar } from 'fullcalendar'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new Calendar(calendarEl, {
|
||||
initialView: 'dayGridMonth'
|
||||
})
|
||||
calendar.render()
|
||||
})
|
||||
```
|
101
fullcalendar-main/bundle/examples/background-events.html
Normal file
101
fullcalendar-main/bundle/examples/background-events.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
|
||||
},
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
businessHours: true, // display business hours
|
||||
editable: true,
|
||||
selectable: true,
|
||||
events: [
|
||||
{
|
||||
title: 'Business Lunch',
|
||||
start: '2023-01-03T13:00:00',
|
||||
constraint: 'businessHours'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-13T11:00:00',
|
||||
constraint: 'availableForMeeting', // defined below
|
||||
color: '#257e4a'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-18',
|
||||
end: '2023-01-20'
|
||||
},
|
||||
{
|
||||
title: 'Party',
|
||||
start: '2023-01-29T20:00:00'
|
||||
},
|
||||
|
||||
// areas where "Meeting" must be dropped
|
||||
{
|
||||
groupId: 'availableForMeeting',
|
||||
start: '2023-01-11T10:00:00',
|
||||
end: '2023-01-11T16:00:00',
|
||||
display: 'background'
|
||||
},
|
||||
{
|
||||
groupId: 'availableForMeeting',
|
||||
start: '2023-01-13T10:00:00',
|
||||
end: '2023-01-13T16:00:00',
|
||||
display: 'background'
|
||||
},
|
||||
|
||||
// red areas where no events can be dropped
|
||||
{
|
||||
start: '2023-01-24',
|
||||
end: '2023-01-28',
|
||||
overlap: false,
|
||||
display: 'background',
|
||||
color: '#ff9f89'
|
||||
},
|
||||
{
|
||||
start: '2023-01-06',
|
||||
end: '2023-01-08',
|
||||
overlap: false,
|
||||
display: 'background',
|
||||
color: '#ff9f89'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
104
fullcalendar-main/bundle/examples/daygrid-views.html
Normal file
104
fullcalendar-main/bundle/examples/daygrid-views.html
Normal file
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prevYear,prev,next,nextYear today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,dayGridWeek,dayGridDay'
|
||||
},
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var srcCalendarEl = document.getElementById('source-calendar');
|
||||
var destCalendarEl = document.getElementById('destination-calendar');
|
||||
|
||||
var srcCalendar = new FullCalendar.Calendar(srcCalendarEl, {
|
||||
editable: true,
|
||||
initialDate: '2023-01-12',
|
||||
events: [
|
||||
{
|
||||
title: 'event1',
|
||||
start: '2023-01-11T10:00:00',
|
||||
end: '2023-01-11T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'event2',
|
||||
start: '2023-01-13T10:00:00',
|
||||
end: '2023-01-13T16:00:00'
|
||||
}
|
||||
],
|
||||
eventLeave: function(info) {
|
||||
console.log('event left!', info.event);
|
||||
}
|
||||
});
|
||||
|
||||
var destCalendar = new FullCalendar.Calendar(destCalendarEl, {
|
||||
initialDate: '2023-01-12',
|
||||
editable: true,
|
||||
droppable: true, // will let it receive events!
|
||||
eventReceive: function(info) {
|
||||
console.log('event received!', info.event);
|
||||
}
|
||||
});
|
||||
|
||||
srcCalendar.render();
|
||||
destCalendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 20px 0 0 20px;
|
||||
font-size: 14px;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#source-calendar,
|
||||
#destination-calendar {
|
||||
float: left;
|
||||
width: 600px;
|
||||
margin: 0 20px 20px 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='source-calendar'></div>
|
||||
<div id='destination-calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
149
fullcalendar-main/bundle/examples/external-dragging-builtin.html
Normal file
149
fullcalendar-main/bundle/examples/external-dragging-builtin.html
Normal file
|
@ -0,0 +1,149 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
/* initialize the external events
|
||||
-----------------------------------------------------------------*/
|
||||
|
||||
var containerEl = document.getElementById('external-events-list');
|
||||
new FullCalendar.Draggable(containerEl, {
|
||||
itemSelector: '.fc-event',
|
||||
eventData: function(eventEl) {
|
||||
return {
|
||||
title: eventEl.innerText.trim()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//// the individual way to do it
|
||||
// var containerEl = document.getElementById('external-events-list');
|
||||
// var eventEls = Array.prototype.slice.call(
|
||||
// containerEl.querySelectorAll('.fc-event')
|
||||
// );
|
||||
// eventEls.forEach(function(eventEl) {
|
||||
// new FullCalendar.Draggable(eventEl, {
|
||||
// eventData: {
|
||||
// title: eventEl.innerText.trim(),
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
/* initialize the calendar
|
||||
-----------------------------------------------------------------*/
|
||||
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
editable: true,
|
||||
droppable: true, // this allows things to be dropped onto the calendar
|
||||
drop: function(arg) {
|
||||
// is the "remove after drop" checkbox checked?
|
||||
if (document.getElementById('drop-remove').checked) {
|
||||
// if so, remove the element from the "Draggable Events" list
|
||||
arg.draggedEl.parentNode.removeChild(arg.draggedEl);
|
||||
}
|
||||
}
|
||||
});
|
||||
calendar.render();
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin-top: 40px;
|
||||
font-size: 14px;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#external-events {
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
width: 150px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #ccc;
|
||||
background: #eee;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#external-events h4 {
|
||||
font-size: 16px;
|
||||
margin-top: 0;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
#external-events .fc-event {
|
||||
margin: 3px 0;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
#external-events p {
|
||||
margin: 1.5em 0;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#external-events p input {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#calendar-wrap {
|
||||
margin-left: 200px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='wrap'>
|
||||
|
||||
<div id='external-events'>
|
||||
<h4>Draggable Events</h4>
|
||||
|
||||
<div id='external-events-list'>
|
||||
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
|
||||
<div class='fc-event-main'>My Event 1</div>
|
||||
</div>
|
||||
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
|
||||
<div class='fc-event-main'>My Event 2</div>
|
||||
</div>
|
||||
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
|
||||
<div class='fc-event-main'>My Event 3</div>
|
||||
</div>
|
||||
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
|
||||
<div class='fc-event-main'>My Event 4</div>
|
||||
</div>
|
||||
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
|
||||
<div class='fc-event-main'>My Event 5</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<input type='checkbox' id='drop-remove' />
|
||||
<label for='drop-remove'>remove after drop</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id='calendar-wrap'>
|
||||
<div id='calendar'></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
125
fullcalendar-main/bundle/examples/full-height.html
Normal file
125
fullcalendar-main/bundle/examples/full-height.html
Normal file
|
@ -0,0 +1,125 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
height: '100%',
|
||||
expandRows: true,
|
||||
slotMinTime: '08:00',
|
||||
slotMaxTime: '20:00',
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
initialView: 'dayGridMonth',
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
selectable: true,
|
||||
nowIndicator: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01',
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
html, body {
|
||||
overflow: hidden; /* don't do scrollbars */
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.fc-header-toolbar {
|
||||
/*
|
||||
the calendar will be butting up against the edges,
|
||||
but let's scoot in the header's buttons
|
||||
*/
|
||||
padding-top: 1em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar-container'>
|
||||
<div id='calendar'></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
76
fullcalendar-main/bundle/examples/list-sticky-header.html
Normal file
76
fullcalendar-main/bundle/examples/list-sticky-header.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
height: 'auto',
|
||||
// stickyHeaderDates: false, // for disabling
|
||||
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'listMonth,listYear'
|
||||
},
|
||||
|
||||
// customize the button names,
|
||||
// otherwise they'd all just say "list"
|
||||
views: {
|
||||
listMonth: { buttonText: 'list month' },
|
||||
listYear: { buttonText: 'list year' }
|
||||
},
|
||||
|
||||
initialView: 'listYear',
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
events: [
|
||||
{
|
||||
title: 'repeating event 1',
|
||||
daysOfWeek: [ 1, 2, 3 ],
|
||||
duration: '00:30'
|
||||
},
|
||||
{
|
||||
title: 'repeating event 2',
|
||||
daysOfWeek: [ 1, 2, 3 ],
|
||||
duration: '00:30'
|
||||
},
|
||||
{
|
||||
title: 'repeating event 3',
|
||||
daysOfWeek: [ 1, 2, 3 ],
|
||||
duration: '00:30'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
114
fullcalendar-main/bundle/examples/list-views.html
Normal file
114
fullcalendar-main/bundle/examples/list-views.html
Normal file
|
@ -0,0 +1,114 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'listDay,listWeek'
|
||||
},
|
||||
|
||||
// customize the button names,
|
||||
// otherwise they'd all just say "list"
|
||||
views: {
|
||||
listDay: { buttonText: 'list day' },
|
||||
listWeek: { buttonText: 'list week' }
|
||||
},
|
||||
|
||||
initialView: 'listWeek',
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
100
fullcalendar-main/bundle/examples/month-view.html
Normal file
100
fullcalendar-main/bundle/examples/month-view.html
Normal file
|
@ -0,0 +1,100 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialDate: '2023-01-12',
|
||||
editable: true,
|
||||
selectable: true,
|
||||
businessHours: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
110
fullcalendar-main/bundle/examples/multimonth-view.html
Normal file
110
fullcalendar-main/bundle/examples/multimonth-view.html
Normal file
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'multiMonthYear,dayGridMonth,timeGridWeek'
|
||||
},
|
||||
initialView: 'multiMonthYear',
|
||||
initialDate: '2023-01-12',
|
||||
editable: true,
|
||||
selectable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
// multiMonthMaxColumns: 1, // guarantee single column
|
||||
// showNonCurrentDates: true,
|
||||
// fixedWeekCount: false,
|
||||
// businessHours: true,
|
||||
// weekends: false,
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
107
fullcalendar-main/bundle/examples/multiweek-view.html
Normal file
107
fullcalendar-main/bundle/examples/multiweek-view.html
Normal file
|
@ -0,0 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridYear,dayGridMonth,timeGridWeek'
|
||||
},
|
||||
initialView: 'dayGridYear',
|
||||
initialDate: '2023-01-12',
|
||||
editable: true,
|
||||
selectable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
// businessHours: true,
|
||||
// weekends: false,
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
108
fullcalendar-main/bundle/examples/natural-height.html
Normal file
108
fullcalendar-main/bundle/examples/natural-height.html
Normal file
|
@ -0,0 +1,108 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialDate: '2023-01-12',
|
||||
initialView: 'timeGridWeek',
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
height: 'auto',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
selectable: true,
|
||||
selectMirror: true,
|
||||
nowIndicator: true,
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01',
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
123
fullcalendar-main/bundle/examples/selectable.html
Normal file
123
fullcalendar-main/bundle/examples/selectable.html
Normal file
|
@ -0,0 +1,123 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
},
|
||||
initialDate: '2023-01-12',
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
selectable: true,
|
||||
selectMirror: true,
|
||||
select: function(arg) {
|
||||
var title = prompt('Event Title:');
|
||||
if (title) {
|
||||
calendar.addEvent({
|
||||
title: title,
|
||||
start: arg.start,
|
||||
end: arg.end,
|
||||
allDay: arg.allDay
|
||||
})
|
||||
}
|
||||
calendar.unselect()
|
||||
},
|
||||
eventClick: function(arg) {
|
||||
if (confirm('Are you sure you want to delete this event?')) {
|
||||
arg.event.remove()
|
||||
}
|
||||
},
|
||||
editable: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01'
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
108
fullcalendar-main/bundle/examples/timegrid-views.html
Normal file
108
fullcalendar-main/bundle/examples/timegrid-views.html
Normal file
|
@ -0,0 +1,108 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<script src='../dist/index.global.js'></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
initialDate: '2023-01-12',
|
||||
initialView: 'timeGridWeek',
|
||||
nowIndicator: true,
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
||||
},
|
||||
navLinks: true, // can click day/week names to navigate views
|
||||
editable: true,
|
||||
selectable: true,
|
||||
selectMirror: true,
|
||||
dayMaxEvents: true, // allow "more" link when too many events
|
||||
events: [
|
||||
{
|
||||
title: 'All Day Event',
|
||||
start: '2023-01-01',
|
||||
},
|
||||
{
|
||||
title: 'Long Event',
|
||||
start: '2023-01-07',
|
||||
end: '2023-01-10'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-09T16:00:00'
|
||||
},
|
||||
{
|
||||
groupId: 999,
|
||||
title: 'Repeating Event',
|
||||
start: '2023-01-16T16:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Conference',
|
||||
start: '2023-01-11',
|
||||
end: '2023-01-13'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T10:30:00',
|
||||
end: '2023-01-12T12:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Lunch',
|
||||
start: '2023-01-12T12:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Meeting',
|
||||
start: '2023-01-12T14:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Happy Hour',
|
||||
start: '2023-01-12T17:30:00'
|
||||
},
|
||||
{
|
||||
title: 'Dinner',
|
||||
start: '2023-01-12T20:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Birthday Party',
|
||||
start: '2023-01-13T07:00:00'
|
||||
},
|
||||
{
|
||||
title: 'Click for Google',
|
||||
url: 'http://google.com/',
|
||||
start: '2023-01-28'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style>
|
||||
|
||||
body {
|
||||
margin: 40px 10px;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='calendar'></div>
|
||||
|
||||
</body>
|
||||
</html>
|
49
fullcalendar-main/bundle/package.json
Normal file
49
fullcalendar-main/bundle/package.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "fullcalendar",
|
||||
"version": "6.1.11",
|
||||
"title": "FullCalendar Standard Bundle",
|
||||
"description": "Easily render a full-sized drag & drop calendar with a combination of standard plugins",
|
||||
"homepage": "https://fullcalendar.io/docs/initialize-globals",
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "~6.1.11",
|
||||
"@fullcalendar/daygrid": "~6.1.11",
|
||||
"@fullcalendar/interaction": "~6.1.11",
|
||||
"@fullcalendar/list": "~6.1.11",
|
||||
"@fullcalendar/multimonth": "~6.1.11",
|
||||
"@fullcalendar/timegrid": "~6.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullcalendar-scripts/standard": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "standard-scripts pkg:build",
|
||||
"clean": "standard-scripts pkg:clean",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"type": "module",
|
||||
"tsConfig": {
|
||||
"extends": "@fullcalendar-scripts/standard/config/tsconfig.browser.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist/.tsout"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
},
|
||||
"buildConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"iife": true
|
||||
}
|
||||
},
|
||||
"iifeGlobals": {
|
||||
".": "FullCalendar",
|
||||
"*": ""
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"directory": "./dist",
|
||||
"linkDirectory": true
|
||||
}
|
||||
}
|
5
fullcalendar-main/bundle/src/index.global.ts
Normal file
5
fullcalendar-main/bundle/src/index.global.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import * as Internal from '@fullcalendar/core/internal'
|
||||
import * as Preact from '@fullcalendar/core/preact'
|
||||
|
||||
export { Internal, Preact }
|
||||
export * from './index.js'
|
17
fullcalendar-main/bundle/src/index.ts
Normal file
17
fullcalendar-main/bundle/src/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { globalPlugins } from '@fullcalendar/core'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import listPlugin from '@fullcalendar/list'
|
||||
import multiMonthPlugin from '@fullcalendar/multimonth'
|
||||
|
||||
globalPlugins.push(
|
||||
interactionPlugin,
|
||||
dayGridPlugin,
|
||||
timeGridPlugin,
|
||||
listPlugin,
|
||||
multiMonthPlugin,
|
||||
)
|
||||
|
||||
export * from '@fullcalendar/core'
|
||||
export * from '@fullcalendar/interaction' // for Draggable
|
47
fullcalendar-main/package.json
Normal file
47
fullcalendar-main/package.json
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "@fullcalendar-monorepos/standard",
|
||||
"description": "Full-sized drag & drop event calendar in JavaScript",
|
||||
"version": "6.1.11",
|
||||
"keywords": [
|
||||
"calendar",
|
||||
"event",
|
||||
"full-sized",
|
||||
"fullcalendar"
|
||||
],
|
||||
"homepage": "https://fullcalendar.io",
|
||||
"bugs": "https://fullcalendar.io/reporting-bugs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fullcalendar/fullcalendar.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Adam Shaw",
|
||||
"email": "arshaw@arshaw.com",
|
||||
"url": "http://arshaw.com/"
|
||||
},
|
||||
"copyright": "2023 Adam Shaw",
|
||||
"devDependencies": {
|
||||
"@fullcalendar-scripts/standard": "*"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"postinstall": "standard-scripts postinstall",
|
||||
"lint": "standard-scripts lint",
|
||||
"build": "standard-scripts build",
|
||||
"dev": "standard-scripts dev",
|
||||
"test": "standard-scripts test",
|
||||
"test:dev": "standard-scripts test --dev",
|
||||
"clean": "standard-scripts clean",
|
||||
"ci": "pnpm run lint && pnpm run build && pnpm run test"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=7.9.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"jasmine-jquery@2.1.1": "scripts/patches/jasmine-jquery@2.1.1.patch"
|
||||
}
|
||||
}
|
||||
}
|
4
fullcalendar-main/packages/bootstrap4/.eslintrc.cjs
Normal file
4
fullcalendar-main/packages/bootstrap4/.eslintrc.cjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: require.resolve('@fullcalendar-scripts/standard/config/eslint.pkg.browser.cjs'),
|
||||
}
|
46
fullcalendar-main/packages/bootstrap4/README.md
Normal file
46
fullcalendar-main/packages/bootstrap4/README.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
# FullCalendar Bootstrap 4 Plugin
|
||||
|
||||
[Bootstrap 4](https://getbootstrap.com/docs/4.6/getting-started/introduction/) theme for [FullCalendar](https://fullcalendar.io)
|
||||
|
||||
> For [Bootstrap 5](https://getbootstrap.com/), use the [@fullcalendar/bootstrap5](https://github.com/fullcalendar/fullcalendar/tree/main/packages/bootstrap5) package
|
||||
|
||||
## Installation
|
||||
|
||||
First, ensure the necessary Bootstrap packages are installed:
|
||||
|
||||
```sh
|
||||
npm install bootstrap@4 @fortawesome/fontawesome-free
|
||||
```
|
||||
|
||||
Then, install the FullCalendar core package, the Bootstrap plugin, and any other plugins (like [daygrid](https://fullcalendar.io/docs/month-view)):
|
||||
|
||||
```sh
|
||||
npm install @fullcalendar/core @fullcalendar/bootstrap @fullcalendar/daygrid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Instantiate a Calendar with the necessary plugins and options:
|
||||
|
||||
```js
|
||||
import { Calendar } from '@fullcalendar/core'
|
||||
import bootstrapPlugin from '@fullcalendar/bootstrap'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
|
||||
// import third-party stylesheets directly from your JS
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.css' // needs additional webpack config!
|
||||
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new Calendar(calendarEl, {
|
||||
plugins: [
|
||||
bootstrapPlugin,
|
||||
dayGridPlugin
|
||||
],
|
||||
themeSystem: 'bootstrap', // important!
|
||||
initialView: 'dayGridMonth'
|
||||
})
|
||||
|
||||
calendar.render()
|
||||
```
|
50
fullcalendar-main/packages/bootstrap4/package.json
Normal file
50
fullcalendar-main/packages/bootstrap4/package.json
Normal file
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "@fullcalendar/bootstrap",
|
||||
"version": "6.1.11",
|
||||
"title": "FullCalendar Bootstrap 4 Plugin",
|
||||
"description": "Bootstrap 4 theme for FullCalendar",
|
||||
"keywords": [
|
||||
"bootstrap",
|
||||
"bootstrap4"
|
||||
],
|
||||
"homepage": "https://fullcalendar.io/docs/bootstrap4",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullcalendar/core": "~6.1.11",
|
||||
"@fullcalendar-scripts/standard": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "standard-scripts pkg:build",
|
||||
"clean": "standard-scripts pkg:clean",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"type": "module",
|
||||
"tsConfig": {
|
||||
"extends": "@fullcalendar-scripts/standard/config/tsconfig.browser.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist/.tsout"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
},
|
||||
"buildConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"iife": true
|
||||
},
|
||||
"./internal": {}
|
||||
},
|
||||
"iifeGlobals": {
|
||||
".": "FullCalendar.Bootstrap",
|
||||
"./internal": "FullCalendar.Bootstrap.Internal"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"directory": "./dist",
|
||||
"linkDirectory": true
|
||||
}
|
||||
}
|
37
fullcalendar-main/packages/bootstrap4/src/BootstrapTheme.ts
Normal file
37
fullcalendar-main/packages/bootstrap4/src/BootstrapTheme.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Theme } from '@fullcalendar/core/internal'
|
||||
|
||||
class BootstrapTheme extends Theme {
|
||||
}
|
||||
|
||||
BootstrapTheme.prototype.classes = {
|
||||
root: 'fc-theme-bootstrap', // TODO: compute this off of registered theme name
|
||||
table: 'table-bordered', // don't attache the `table` class. we only want the borders, not any layout
|
||||
tableCellShaded: 'table-active',
|
||||
buttonGroup: 'btn-group',
|
||||
button: 'btn btn-primary',
|
||||
buttonActive: 'active',
|
||||
popover: 'popover',
|
||||
popoverHeader: 'popover-header',
|
||||
popoverContent: 'popover-body',
|
||||
}
|
||||
|
||||
BootstrapTheme.prototype.baseIconClass = 'fa'
|
||||
BootstrapTheme.prototype.iconClasses = {
|
||||
close: 'fa-times',
|
||||
prev: 'fa-chevron-left',
|
||||
next: 'fa-chevron-right',
|
||||
prevYear: 'fa-angle-double-left',
|
||||
nextYear: 'fa-angle-double-right',
|
||||
}
|
||||
BootstrapTheme.prototype.rtlIconClasses = {
|
||||
prev: 'fa-chevron-right',
|
||||
next: 'fa-chevron-left',
|
||||
prevYear: 'fa-angle-double-right',
|
||||
nextYear: 'fa-angle-double-left',
|
||||
}
|
||||
|
||||
BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome' // TODO: make TS-friendly. move the option-processing into this plugin
|
||||
BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'
|
||||
BootstrapTheme.prototype.iconOverridePrefix = 'fa-'
|
||||
|
||||
export { BootstrapTheme }
|
12
fullcalendar-main/packages/bootstrap4/src/index.css
Normal file
12
fullcalendar-main/packages/bootstrap4/src/index.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
.fc-theme-bootstrap {
|
||||
|
||||
& a:not([href]) {
|
||||
color: inherit; // natural color for navlinks
|
||||
}
|
||||
|
||||
& .fc-more-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { globalPlugins } from '@fullcalendar/core'
|
||||
import plugin from './index.js'
|
||||
import * as Internal from './internal.js'
|
||||
|
||||
globalPlugins.push(plugin)
|
||||
|
||||
export { plugin as default, Internal }
|
||||
export * from './index.js'
|
10
fullcalendar-main/packages/bootstrap4/src/index.ts
Normal file
10
fullcalendar-main/packages/bootstrap4/src/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createPlugin, PluginDef } from '@fullcalendar/core'
|
||||
import { BootstrapTheme } from './BootstrapTheme.js'
|
||||
import './index.css'
|
||||
|
||||
export default createPlugin({
|
||||
name: '<%= pkgName %>',
|
||||
themeClasses: {
|
||||
bootstrap: BootstrapTheme,
|
||||
},
|
||||
}) as PluginDef
|
3
fullcalendar-main/packages/bootstrap4/src/internal.ts
Normal file
3
fullcalendar-main/packages/bootstrap4/src/internal.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import './index.css'
|
||||
|
||||
export { BootstrapTheme } from './BootstrapTheme.js'
|
4
fullcalendar-main/packages/bootstrap5/.eslintrc.cjs
Normal file
4
fullcalendar-main/packages/bootstrap5/.eslintrc.cjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: require.resolve('@fullcalendar-scripts/standard/config/eslint.pkg.browser.cjs'),
|
||||
}
|
44
fullcalendar-main/packages/bootstrap5/README.md
Normal file
44
fullcalendar-main/packages/bootstrap5/README.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
# FullCalendar Bootstrap 5 Plugin
|
||||
|
||||
[Bootstrap 5](https://getbootstrap.com/) theme for [FullCalendar](https://fullcalendar.io)
|
||||
|
||||
## Installation
|
||||
|
||||
First, ensure the necessary Bootstrap packages are installed:
|
||||
|
||||
```sh
|
||||
npm install bootstrap@5 bootstrap-icons
|
||||
```
|
||||
|
||||
Then, install the FullCalendar core package, the Bootstrap plugin, and any other plugins (like [daygrid](https://fullcalendar.io/docs/month-view)):
|
||||
|
||||
```sh
|
||||
npm install @fullcalendar/core @fullcalendar/bootstrap5 @fullcalendar/daygrid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Instantiate a Calendar with the necessary plugins and options:
|
||||
|
||||
```js
|
||||
import { Calendar } from '@fullcalendar/core'
|
||||
import bootstrap5Plugin from '@fullcalendar/bootstrap5'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
|
||||
// import bootstrap stylesheets directly from your JS
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css' // needs additional webpack config!
|
||||
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new Calendar(calendarEl, {
|
||||
plugins: [
|
||||
bootstrap5Plugin,
|
||||
dayGridPlugin
|
||||
],
|
||||
themeSystem: 'bootstrap5', // important!
|
||||
initialView: 'dayGridMonth'
|
||||
})
|
||||
|
||||
calendar.render()
|
||||
```
|
50
fullcalendar-main/packages/bootstrap5/package.json
Normal file
50
fullcalendar-main/packages/bootstrap5/package.json
Normal file
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "@fullcalendar/bootstrap5",
|
||||
"version": "6.1.11",
|
||||
"title": "FullCalendar Bootstrap 5 Plugin",
|
||||
"description": "Bootstrap 5 theme for FullCalendar",
|
||||
"keywords": [
|
||||
"bootstrap",
|
||||
"bootstrap5"
|
||||
],
|
||||
"homepage": "https://fullcalendar.io/docs/bootstrap5",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullcalendar/core": "~6.1.11",
|
||||
"@fullcalendar-scripts/standard": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "standard-scripts pkg:build",
|
||||
"clean": "standard-scripts pkg:clean",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"type": "module",
|
||||
"tsConfig": {
|
||||
"extends": "@fullcalendar-scripts/standard/config/tsconfig.browser.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist/.tsout"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
},
|
||||
"buildConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"iife": true
|
||||
},
|
||||
"./internal": {}
|
||||
},
|
||||
"iifeGlobals": {
|
||||
".": "FullCalendar.Bootstrap5",
|
||||
"./internal": "FullCalendar.Bootstrap5.Internal"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"directory": "./dist",
|
||||
"linkDirectory": true
|
||||
}
|
||||
}
|
37
fullcalendar-main/packages/bootstrap5/src/BootstrapTheme.ts
Normal file
37
fullcalendar-main/packages/bootstrap5/src/BootstrapTheme.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Theme } from '@fullcalendar/core/internal'
|
||||
|
||||
export class BootstrapTheme extends Theme {
|
||||
}
|
||||
|
||||
BootstrapTheme.prototype.classes = {
|
||||
root: 'fc-theme-bootstrap5',
|
||||
tableCellShaded: 'fc-theme-bootstrap5-shaded',
|
||||
buttonGroup: 'btn-group',
|
||||
button: 'btn btn-primary',
|
||||
buttonActive: 'active',
|
||||
popover: 'popover',
|
||||
popoverHeader: 'popover-header',
|
||||
popoverContent: 'popover-body',
|
||||
}
|
||||
|
||||
BootstrapTheme.prototype.baseIconClass = 'bi'
|
||||
BootstrapTheme.prototype.iconClasses = {
|
||||
close: 'bi-x-lg',
|
||||
prev: 'bi-chevron-left',
|
||||
next: 'bi-chevron-right',
|
||||
prevYear: 'bi-chevron-double-left',
|
||||
nextYear: 'bi-chevron-double-right',
|
||||
}
|
||||
BootstrapTheme.prototype.rtlIconClasses = {
|
||||
prev: 'bi-chevron-right',
|
||||
next: 'bi-chevron-left',
|
||||
prevYear: 'bi-chevron-double-right',
|
||||
nextYear: 'bi-chevron-double-left',
|
||||
}
|
||||
|
||||
// wtf
|
||||
BootstrapTheme.prototype.iconOverrideOption = 'buttonIcons' // TODO: make TS-friendly
|
||||
BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'icon'
|
||||
BootstrapTheme.prototype.iconOverridePrefix = 'bi-'
|
||||
|
||||
export { Theme }
|
25
fullcalendar-main/packages/bootstrap5/src/index.css
Normal file
25
fullcalendar-main/packages/bootstrap5/src/index.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
.fc-theme-bootstrap5 {
|
||||
|
||||
& a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
& .fc-list,
|
||||
& .fc-scrollgrid,
|
||||
& td,
|
||||
& th {
|
||||
border: 1px solid var(--bs-gray-400);
|
||||
}
|
||||
|
||||
// HACK: reapply core styles after highe-precedence border statement above
|
||||
& .fc-scrollgrid {
|
||||
border-right-width: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-theme-bootstrap5-shaded {
|
||||
background-color: var(--bs-gray-200);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { globalPlugins } from '@fullcalendar/core'
|
||||
import plugin from './index.js'
|
||||
import * as Internal from './internal.js'
|
||||
|
||||
globalPlugins.push(plugin)
|
||||
|
||||
export { plugin as default, Internal }
|
||||
export * from './index.js'
|
10
fullcalendar-main/packages/bootstrap5/src/index.ts
Normal file
10
fullcalendar-main/packages/bootstrap5/src/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createPlugin, PluginDef } from '@fullcalendar/core'
|
||||
import { BootstrapTheme } from './BootstrapTheme.js'
|
||||
import './index.css'
|
||||
|
||||
export default createPlugin({
|
||||
name: '<%= pkgName %>',
|
||||
themeClasses: {
|
||||
bootstrap5: BootstrapTheme,
|
||||
},
|
||||
}) as PluginDef
|
3
fullcalendar-main/packages/bootstrap5/src/internal.ts
Normal file
3
fullcalendar-main/packages/bootstrap5/src/internal.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import './index.css'
|
||||
|
||||
export { BootstrapTheme } from './BootstrapTheme.js'
|
4
fullcalendar-main/packages/core/.eslintrc.cjs
Normal file
4
fullcalendar-main/packages/core/.eslintrc.cjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: require.resolve('@fullcalendar-scripts/standard/config/eslint.pkg.browser.cjs'),
|
||||
}
|
44
fullcalendar-main/packages/core/README.md
Normal file
44
fullcalendar-main/packages/core/README.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
# FullCalendar Core
|
||||
|
||||
FullCalendar core package for rendering a calendar
|
||||
|
||||
## Installation
|
||||
|
||||
This package is never used alone. Use it with least one plugin (like [daygrid](https://fullcalendar.io/docs/month-view)):
|
||||
|
||||
```sh
|
||||
npm install @fullcalendar/core @fullcalendar/daygrid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, ensure there's a DOM element for your calendar to render into:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div id='calendar'></div>
|
||||
</body>
|
||||
```
|
||||
|
||||
Then, instantiate a Calendar object with [options](https://fullcalendar.io/docs#toc) and call its `render` method:
|
||||
|
||||
```js
|
||||
import { Calendar } from '@fullcalendar/core'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
const calendar = new Calendar(calendarEl, {
|
||||
plugins: [
|
||||
dayGridPlugin
|
||||
// any other plugins
|
||||
],
|
||||
initialView: 'dayGridMonth',
|
||||
weekends: false,
|
||||
events: [
|
||||
{ title: 'Meeting', start: new Date() }
|
||||
]
|
||||
})
|
||||
|
||||
calendar.render()
|
||||
```
|
58
fullcalendar-main/packages/core/package.json
Normal file
58
fullcalendar-main/packages/core/package.json
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"name": "@fullcalendar/core",
|
||||
"version": "6.1.11",
|
||||
"title": "FullCalendar Core",
|
||||
"description": "FullCalendar core package for rendering a calendar",
|
||||
"dependencies": {
|
||||
"preact": "~10.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullcalendar-scripts/standard": "*",
|
||||
"globby": "^13.1.2",
|
||||
"handlebars": "^4.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "standard-scripts pkg:build",
|
||||
"clean": "standard-scripts pkg:clean",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"type": "module",
|
||||
"tsConfig": {
|
||||
"extends": "@fullcalendar-scripts/standard/config/tsconfig.browser.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist/.tsout"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
},
|
||||
"buildConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"iife": true
|
||||
},
|
||||
"./preact": {},
|
||||
"./internal": {},
|
||||
"./locales-all": {
|
||||
"iife": true,
|
||||
"generator": "./scripts/generate-locales-all.js"
|
||||
},
|
||||
"./locales/*": {
|
||||
"iife": true,
|
||||
"iifeGenerator": "./scripts/generate-locale-iife.js"
|
||||
}
|
||||
},
|
||||
"iifeGlobals": {
|
||||
".": "FullCalendar",
|
||||
"./preact": "FullCalendar.Preact",
|
||||
"./internal": "FullCalendar.Internal",
|
||||
"preact": "",
|
||||
"preact/compat": ""
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"directory": "./dist",
|
||||
"linkDirectory": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { join as joinPaths, basename } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { readFile } from 'fs/promises'
|
||||
import handlebars from 'handlebars'
|
||||
|
||||
const thisPkgDir = joinPaths(fileURLToPath(import.meta.url), '../..')
|
||||
const templatePath = joinPaths(thisPkgDir, 'src/locales/global.js.tpl')
|
||||
|
||||
export function getWatchPaths() {
|
||||
return [templatePath, templatePath]
|
||||
}
|
||||
|
||||
export default async function(config) {
|
||||
const localeCode = basename(config.entryAlias)
|
||||
|
||||
const templateText = await readFile(templatePath, 'utf8')
|
||||
const template = handlebars.compile(templateText)
|
||||
const code = template({ localeCode })
|
||||
|
||||
return code
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { join as joinPaths } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { globby } from 'globby'
|
||||
import handlebars from 'handlebars'
|
||||
|
||||
const thisPkgDir = joinPaths(fileURLToPath(import.meta.url), '../..')
|
||||
const templatePath = joinPaths(thisPkgDir, 'src/locales-all.js.tpl')
|
||||
const localesDir = joinPaths(thisPkgDir, 'src/locales')
|
||||
|
||||
export function getWatchPaths() {
|
||||
return [
|
||||
templatePath,
|
||||
localesDir,
|
||||
]
|
||||
}
|
||||
|
||||
export default async function() {
|
||||
const localeFilenames = await globby('*.ts', { cwd: localesDir })
|
||||
const localeCodes = localeFilenames.map((filename) => filename.replace(/\.ts$/, ''))
|
||||
|
||||
const templateText = await readFile(templatePath, 'utf8')
|
||||
const template = handlebars.compile(templateText)
|
||||
const code = template({ localeCodes })
|
||||
|
||||
return code
|
||||
}
|
156
fullcalendar-main/packages/core/src/Calendar.tsx
Normal file
156
fullcalendar-main/packages/core/src/Calendar.tsx
Normal file
|
@ -0,0 +1,156 @@
|
|||
import { CalendarOptions } from './options.js'
|
||||
import { DelayedRunner } from './util/DelayedRunner.js'
|
||||
import { CalendarDataManager } from './reducers/CalendarDataManager.js'
|
||||
import { Action } from './reducers/Action.js'
|
||||
import { CalendarData } from './reducers/data-types.js'
|
||||
import { CalendarRoot } from './CalendarRoot.js'
|
||||
import { CalendarContent } from './CalendarContent.js'
|
||||
import { createElement, render, flushSync } from './preact.js'
|
||||
import { isArraysEqual } from './util/array.js'
|
||||
import { CssDimValue } from './scrollgrid/util.js'
|
||||
import { applyStyleProp } from './util/dom-manip.js'
|
||||
import { RenderId } from './content-inject/RenderId.js'
|
||||
import { CalendarImpl } from './api/CalendarImpl.js'
|
||||
import { ensureElHasStyles } from './styleUtils.js'
|
||||
|
||||
export class Calendar extends CalendarImpl {
|
||||
el: HTMLElement
|
||||
|
||||
private currentData: CalendarData
|
||||
private renderRunner: DelayedRunner
|
||||
private isRendering = false
|
||||
private isRendered = false
|
||||
private currentClassNames: string[] = []
|
||||
private customContentRenderId = 0
|
||||
|
||||
constructor(el: HTMLElement, optionOverrides: CalendarOptions = {}) {
|
||||
super()
|
||||
ensureElHasStyles(el)
|
||||
|
||||
this.el = el
|
||||
this.renderRunner = new DelayedRunner(this.handleRenderRequest)
|
||||
|
||||
new CalendarDataManager({ // eslint-disable-line no-new
|
||||
optionOverrides,
|
||||
calendarApi: this,
|
||||
onAction: this.handleAction,
|
||||
onData: this.handleData,
|
||||
})
|
||||
}
|
||||
|
||||
private handleAction = (action: Action) => {
|
||||
// actions we know we want to render immediately
|
||||
switch (action.type) {
|
||||
case 'SET_EVENT_DRAG':
|
||||
case 'SET_EVENT_RESIZE':
|
||||
this.renderRunner.tryDrain()
|
||||
}
|
||||
}
|
||||
|
||||
private handleData = (data: CalendarData) => {
|
||||
this.currentData = data
|
||||
this.renderRunner.request(data.calendarOptions.rerenderDelay)
|
||||
}
|
||||
|
||||
private handleRenderRequest = () => {
|
||||
if (this.isRendering) {
|
||||
this.isRendered = true
|
||||
let { currentData } = this
|
||||
|
||||
flushSync(() => {
|
||||
render(
|
||||
<CalendarRoot options={currentData.calendarOptions} theme={currentData.theme} emitter={currentData.emitter}>
|
||||
{(classNames, height, isHeightAuto, forPrint) => {
|
||||
this.setClassNames(classNames)
|
||||
this.setHeight(height)
|
||||
|
||||
return (
|
||||
<RenderId.Provider value={this.customContentRenderId}>
|
||||
<CalendarContent
|
||||
isHeightAuto={isHeightAuto}
|
||||
forPrint={forPrint}
|
||||
{...currentData}
|
||||
/>
|
||||
</RenderId.Provider>
|
||||
)
|
||||
}}
|
||||
</CalendarRoot>,
|
||||
this.el,
|
||||
)
|
||||
})
|
||||
} else if (this.isRendered) {
|
||||
this.isRendered = false
|
||||
render(null, this.el)
|
||||
|
||||
this.setClassNames([])
|
||||
this.setHeight('')
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let wasRendering = this.isRendering
|
||||
|
||||
if (!wasRendering) {
|
||||
this.isRendering = true
|
||||
} else {
|
||||
this.customContentRenderId += 1
|
||||
}
|
||||
|
||||
this.renderRunner.request()
|
||||
|
||||
if (wasRendering) {
|
||||
this.updateSize()
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.isRendering) {
|
||||
this.isRendering = false
|
||||
this.renderRunner.request()
|
||||
}
|
||||
}
|
||||
|
||||
updateSize(): void {
|
||||
flushSync(() => {
|
||||
super.updateSize()
|
||||
})
|
||||
}
|
||||
|
||||
batchRendering(func): void {
|
||||
this.renderRunner.pause('batchRendering')
|
||||
func()
|
||||
this.renderRunner.resume('batchRendering')
|
||||
}
|
||||
|
||||
pauseRendering() { // available to plugins
|
||||
this.renderRunner.pause('pauseRendering')
|
||||
}
|
||||
|
||||
resumeRendering() { // available to plugins
|
||||
this.renderRunner.resume('pauseRendering', true)
|
||||
}
|
||||
|
||||
resetOptions(optionOverrides, changedOptionNames?: string[]) {
|
||||
this.currentDataManager.resetOptions(optionOverrides, changedOptionNames)
|
||||
}
|
||||
|
||||
private setClassNames(classNames: string[]) {
|
||||
if (!isArraysEqual(classNames, this.currentClassNames)) {
|
||||
let { classList } = this.el
|
||||
|
||||
for (let className of this.currentClassNames) {
|
||||
classList.remove(className)
|
||||
}
|
||||
|
||||
for (let className of classNames) {
|
||||
classList.add(className)
|
||||
}
|
||||
|
||||
this.currentClassNames = classNames
|
||||
}
|
||||
}
|
||||
|
||||
private setHeight(height: CssDimValue) {
|
||||
applyStyleProp(this.el, 'height', height)
|
||||
}
|
||||
}
|
289
fullcalendar-main/packages/core/src/CalendarContent.tsx
Normal file
289
fullcalendar-main/packages/core/src/CalendarContent.tsx
Normal file
|
@ -0,0 +1,289 @@
|
|||
import { ViewContextType, buildViewContext } from './ViewContext.js'
|
||||
import { ViewSpec } from './structs/view-spec.js'
|
||||
import { ViewProps } from './View.js'
|
||||
import { Toolbar } from './Toolbar.js'
|
||||
import { DateProfileGenerator, DateProfile } from './DateProfileGenerator.js'
|
||||
import { rangeContainsMarker } from './datelib/date-range.js'
|
||||
import { memoize } from './util/memoize.js'
|
||||
import { DateMarker } from './datelib/marker.js'
|
||||
import { CalendarData } from './reducers/data-types.js'
|
||||
import { ViewPropsTransformerClass } from './plugin-system-struct.js'
|
||||
import { createElement, createRef, Fragment, VNode } from './preact.js'
|
||||
import { ViewHarness } from './ViewHarness.js'
|
||||
import {
|
||||
Interaction,
|
||||
InteractionSettingsInput,
|
||||
InteractionClass,
|
||||
parseInteractionSettings,
|
||||
interactionSettingsStore,
|
||||
} from './interactions/interaction.js'
|
||||
import { DateComponent } from './component/DateComponent.js'
|
||||
import { EventClicking } from './interactions/EventClicking.js'
|
||||
import { EventHovering } from './interactions/EventHovering.js'
|
||||
import { getNow } from './reducers/current-date.js'
|
||||
import { CalendarInteraction } from './calendar-utils.js'
|
||||
import { DelayedRunner } from './util/DelayedRunner.js'
|
||||
import { PureComponent } from './vdom-util.js'
|
||||
import { getUniqueDomId } from './util/dom-manip.js'
|
||||
|
||||
export interface CalendarContentProps extends CalendarData {
|
||||
forPrint: boolean
|
||||
isHeightAuto: boolean
|
||||
}
|
||||
|
||||
export class CalendarContent extends PureComponent<CalendarContentProps> {
|
||||
private buildViewContext = memoize(buildViewContext)
|
||||
private buildViewPropTransformers = memoize(buildViewPropTransformers)
|
||||
private buildToolbarProps = memoize(buildToolbarProps)
|
||||
private headerRef = createRef<Toolbar>()
|
||||
private footerRef = createRef<Toolbar>()
|
||||
private interactionsStore: { [componentUid: string]: Interaction[] } = {}
|
||||
private calendarInteractions: CalendarInteraction[]
|
||||
|
||||
// eslint-disable-next-line
|
||||
state = {
|
||||
viewLabelId: getUniqueDomId(),
|
||||
}
|
||||
|
||||
/*
|
||||
renders INSIDE of an outer div
|
||||
*/
|
||||
render() {
|
||||
let { props } = this
|
||||
let { toolbarConfig, options } = props
|
||||
|
||||
let toolbarProps = this.buildToolbarProps(
|
||||
props.viewSpec,
|
||||
props.dateProfile,
|
||||
props.dateProfileGenerator,
|
||||
props.currentDate,
|
||||
getNow(props.options.now, props.dateEnv), // TODO: use NowTimer????
|
||||
props.viewTitle,
|
||||
)
|
||||
|
||||
let viewVGrow = false
|
||||
let viewHeight: string | number = ''
|
||||
let viewAspectRatio: number | undefined
|
||||
|
||||
if (props.isHeightAuto || props.forPrint) {
|
||||
viewHeight = ''
|
||||
} else if (options.height != null) {
|
||||
viewVGrow = true
|
||||
} else if (options.contentHeight != null) {
|
||||
viewHeight = options.contentHeight
|
||||
} else {
|
||||
viewAspectRatio = Math.max(options.aspectRatio, 0.5) // prevent from getting too tall
|
||||
}
|
||||
|
||||
let viewContext = this.buildViewContext(
|
||||
props.viewSpec,
|
||||
props.viewApi,
|
||||
props.options,
|
||||
props.dateProfileGenerator,
|
||||
props.dateEnv,
|
||||
props.theme,
|
||||
props.pluginHooks,
|
||||
props.dispatch,
|
||||
props.getCurrentData,
|
||||
props.emitter,
|
||||
props.calendarApi,
|
||||
this.registerInteractiveComponent,
|
||||
this.unregisterInteractiveComponent,
|
||||
)
|
||||
|
||||
let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
|
||||
? this.state.viewLabelId
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<ViewContextType.Provider value={viewContext}>
|
||||
{toolbarConfig.header && (
|
||||
<Toolbar
|
||||
ref={this.headerRef}
|
||||
extraClassName="fc-header-toolbar"
|
||||
model={toolbarConfig.header}
|
||||
titleId={viewLabelId}
|
||||
{...toolbarProps}
|
||||
/>
|
||||
)}
|
||||
<ViewHarness
|
||||
liquid={viewVGrow}
|
||||
height={viewHeight}
|
||||
aspectRatio={viewAspectRatio}
|
||||
labeledById={viewLabelId}
|
||||
>
|
||||
{this.renderView(props)}
|
||||
{this.buildAppendContent()}
|
||||
</ViewHarness>
|
||||
{toolbarConfig.footer && (
|
||||
<Toolbar
|
||||
ref={this.footerRef}
|
||||
extraClassName="fc-footer-toolbar"
|
||||
model={toolbarConfig.footer}
|
||||
titleId=""
|
||||
{...toolbarProps}
|
||||
/>
|
||||
)}
|
||||
</ViewContextType.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { props } = this
|
||||
|
||||
this.calendarInteractions = props.pluginHooks.calendarInteractions
|
||||
.map((CalendarInteractionClass) => new CalendarInteractionClass(props))
|
||||
|
||||
window.addEventListener('resize', this.handleWindowResize)
|
||||
|
||||
let { propSetHandlers } = props.pluginHooks
|
||||
for (let propName in propSetHandlers) {
|
||||
propSetHandlers[propName](props[propName], props)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: CalendarContentProps) {
|
||||
let { props } = this
|
||||
|
||||
let { propSetHandlers } = props.pluginHooks
|
||||
for (let propName in propSetHandlers) {
|
||||
if (props[propName] !== prevProps[propName]) {
|
||||
propSetHandlers[propName](props[propName], props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleWindowResize)
|
||||
this.resizeRunner.clear()
|
||||
|
||||
for (let interaction of this.calendarInteractions) {
|
||||
interaction.destroy()
|
||||
}
|
||||
|
||||
this.props.emitter.trigger('_unmount')
|
||||
}
|
||||
|
||||
buildAppendContent(): VNode {
|
||||
let { props } = this
|
||||
|
||||
let children = props.pluginHooks.viewContainerAppends.map(
|
||||
(buildAppendContent) => buildAppendContent(props),
|
||||
)
|
||||
|
||||
return createElement(Fragment, {}, ...children)
|
||||
}
|
||||
|
||||
renderView(props: CalendarContentProps) {
|
||||
let { pluginHooks } = props
|
||||
let { viewSpec } = props
|
||||
|
||||
let viewProps: ViewProps = {
|
||||
dateProfile: props.dateProfile,
|
||||
businessHours: props.businessHours,
|
||||
eventStore: props.renderableEventStore, // !
|
||||
eventUiBases: props.eventUiBases,
|
||||
dateSelection: props.dateSelection,
|
||||
eventSelection: props.eventSelection,
|
||||
eventDrag: props.eventDrag,
|
||||
eventResize: props.eventResize,
|
||||
isHeightAuto: props.isHeightAuto,
|
||||
forPrint: props.forPrint,
|
||||
}
|
||||
|
||||
let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers)
|
||||
|
||||
for (let transformer of transformers) {
|
||||
Object.assign(
|
||||
viewProps,
|
||||
transformer.transform(viewProps, props),
|
||||
)
|
||||
}
|
||||
|
||||
let ViewComponent = viewSpec.component
|
||||
|
||||
return (
|
||||
<ViewComponent {...viewProps} />
|
||||
)
|
||||
}
|
||||
|
||||
// Component Registration
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
registerInteractiveComponent = (component: DateComponent<any>, settingsInput: InteractionSettingsInput) => {
|
||||
let settings = parseInteractionSettings(component, settingsInput)
|
||||
let DEFAULT_INTERACTIONS: InteractionClass[] = [
|
||||
EventClicking,
|
||||
EventHovering,
|
||||
]
|
||||
let interactionClasses: InteractionClass[] = DEFAULT_INTERACTIONS.concat(
|
||||
this.props.pluginHooks.componentInteractions,
|
||||
)
|
||||
let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings))
|
||||
|
||||
this.interactionsStore[component.uid] = interactions
|
||||
interactionSettingsStore[component.uid] = settings
|
||||
}
|
||||
|
||||
unregisterInteractiveComponent = (component: DateComponent<any>) => {
|
||||
let listeners = this.interactionsStore[component.uid]
|
||||
|
||||
if (listeners) {
|
||||
for (let listener of listeners) {
|
||||
listener.destroy()
|
||||
}
|
||||
delete this.interactionsStore[component.uid]
|
||||
}
|
||||
|
||||
delete interactionSettingsStore[component.uid]
|
||||
}
|
||||
|
||||
// Resizing
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
resizeRunner = new DelayedRunner(() => {
|
||||
this.props.emitter.trigger('_resize', true) // should window resizes be considered "forced" ?
|
||||
this.props.emitter.trigger('windowResize', { view: this.props.viewApi })
|
||||
})
|
||||
|
||||
handleWindowResize = (ev: UIEvent) => {
|
||||
let { options } = this.props
|
||||
|
||||
if (
|
||||
options.handleWindowResize &&
|
||||
ev.target === window // avoid jqui events
|
||||
) {
|
||||
this.resizeRunner.request(options.windowResizeDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildToolbarProps(
|
||||
viewSpec: ViewSpec,
|
||||
dateProfile: DateProfile,
|
||||
dateProfileGenerator: DateProfileGenerator,
|
||||
currentDate: DateMarker,
|
||||
now: DateMarker,
|
||||
title: string,
|
||||
) {
|
||||
// don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
|
||||
let todayInfo = dateProfileGenerator.build(now, undefined, false) // TODO: need `undefined` or else INFINITE LOOP for some reason
|
||||
let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false)
|
||||
let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false)
|
||||
|
||||
return {
|
||||
title,
|
||||
activeButton: viewSpec.type,
|
||||
navUnit: viewSpec.singleUnit,
|
||||
isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
|
||||
isPrevEnabled: prevInfo.isValid,
|
||||
isNextEnabled: nextInfo.isValid,
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function buildViewPropTransformers(theClasses: ViewPropsTransformerClass[]) {
|
||||
return theClasses.map((TheClass) => new TheClass())
|
||||
}
|
17
fullcalendar-main/packages/core/src/CalendarContext.ts
Normal file
17
fullcalendar-main/packages/core/src/CalendarContext.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { DateEnv } from './datelib/env.js'
|
||||
import { BaseOptionsRefined, CalendarListeners } from './options.js'
|
||||
import { PluginHooks } from './plugin-system-struct.js'
|
||||
import { Emitter } from './common/Emitter.js'
|
||||
import { Action } from './reducers/Action.js'
|
||||
import { CalendarImpl } from './api/CalendarImpl.js'
|
||||
import { CalendarData } from './reducers/data-types.js'
|
||||
|
||||
export interface CalendarContext {
|
||||
dateEnv: DateEnv
|
||||
options: BaseOptionsRefined // does not have calendar-specific properties. aims to be compatible with ViewOptionsRefined
|
||||
pluginHooks: PluginHooks
|
||||
emitter: Emitter<CalendarListeners>
|
||||
dispatch(action: Action): void
|
||||
getCurrentData(): CalendarData
|
||||
calendarApi: CalendarImpl
|
||||
}
|
70
fullcalendar-main/packages/core/src/CalendarRoot.tsx
Normal file
70
fullcalendar-main/packages/core/src/CalendarRoot.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { ComponentChildren, flushSync } from './preact.js'
|
||||
import { BaseComponent } from './vdom-util.js'
|
||||
import { CssDimValue } from './scrollgrid/util.js'
|
||||
import { CalendarOptions, CalendarListeners } from './options.js'
|
||||
import { Theme } from './theme/Theme.js'
|
||||
import { getCanVGrowWithinCell } from './util/table-styling.js'
|
||||
import { Emitter } from './common/Emitter.js'
|
||||
|
||||
export interface CalendarRootProps {
|
||||
options: CalendarOptions
|
||||
theme: Theme
|
||||
emitter: Emitter<CalendarListeners>
|
||||
children: (classNames: string[], height: CssDimValue, isHeightAuto: boolean, forPrint: boolean) => ComponentChildren
|
||||
}
|
||||
|
||||
interface CalendarRootState {
|
||||
forPrint: boolean
|
||||
}
|
||||
|
||||
export class CalendarRoot extends BaseComponent<CalendarRootProps, CalendarRootState> {
|
||||
state = {
|
||||
forPrint: false,
|
||||
}
|
||||
|
||||
render() {
|
||||
let { props } = this
|
||||
let { options } = props
|
||||
let { forPrint } = this.state
|
||||
|
||||
let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'
|
||||
let height = (!isHeightAuto && options.height != null) ? options.height : ''
|
||||
|
||||
let classNames: string[] = [
|
||||
'fc',
|
||||
forPrint ? 'fc-media-print' : 'fc-media-screen',
|
||||
`fc-direction-${options.direction}`,
|
||||
props.theme.getClass('root'),
|
||||
]
|
||||
|
||||
if (!getCanVGrowWithinCell()) {
|
||||
classNames.push('fc-liquid-hack')
|
||||
}
|
||||
|
||||
return props.children(classNames, height, isHeightAuto, forPrint)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { emitter } = this.props
|
||||
emitter.on('_beforeprint', this.handleBeforePrint)
|
||||
emitter.on('_afterprint', this.handleAfterPrint)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let { emitter } = this.props
|
||||
emitter.off('_beforeprint', this.handleBeforePrint)
|
||||
emitter.off('_afterprint', this.handleAfterPrint)
|
||||
}
|
||||
|
||||
handleBeforePrint = () => {
|
||||
flushSync(() => {
|
||||
this.setState({ forPrint: true })
|
||||
})
|
||||
}
|
||||
|
||||
handleAfterPrint = () => {
|
||||
flushSync(() => {
|
||||
this.setState({ forPrint: false })
|
||||
})
|
||||
}
|
||||
}
|
455
fullcalendar-main/packages/core/src/DateProfileGenerator.ts
Normal file
455
fullcalendar-main/packages/core/src/DateProfileGenerator.ts
Normal file
|
@ -0,0 +1,455 @@
|
|||
import { DateMarker, startOfDay, addDays } from './datelib/marker.js'
|
||||
import { Duration, createDuration, asRoughDays, asRoughMs, greatestDurationDenominator } from './datelib/duration.js'
|
||||
import {
|
||||
DateRange,
|
||||
OpenDateRange,
|
||||
constrainMarkerToRange,
|
||||
intersectRanges,
|
||||
rangeContainsMarker,
|
||||
rangesIntersect,
|
||||
parseRange,
|
||||
DateRangeInput,
|
||||
} from './datelib/date-range.js'
|
||||
import { DateEnv, DateInput } from './datelib/env.js'
|
||||
import { computeVisibleDayRange } from './util/date.js'
|
||||
import { getNow } from './reducers/current-date.js'
|
||||
import { CalendarImpl } from './api/CalendarImpl.js'
|
||||
|
||||
export interface DateProfile {
|
||||
currentDate: DateMarker
|
||||
isValid: boolean
|
||||
validRange: OpenDateRange // dates in past/present/future the user can interact with
|
||||
renderRange: DateRange // dates that get rendered (even if they're completely blank)
|
||||
activeRange: DateRange | null // dates where content is rendered
|
||||
currentRange: DateRange // dates for current interval (TODO: include slotMinTime/slotMaxTime?)
|
||||
currentRangeUnit: string
|
||||
isRangeAllDay: boolean
|
||||
dateIncrement: Duration
|
||||
slotMinTime: Duration
|
||||
slotMaxTime: Duration
|
||||
}
|
||||
|
||||
export interface DateProfileGeneratorProps extends DateProfileOptions {
|
||||
dateProfileGeneratorClass: DateProfileGeneratorClass // not used by DateProfileGenerator itself
|
||||
duration: Duration
|
||||
durationUnit: string
|
||||
usesMinMaxTime: boolean
|
||||
dateEnv: DateEnv
|
||||
calendarApi: CalendarImpl
|
||||
}
|
||||
|
||||
export interface DateProfileOptions {
|
||||
slotMinTime: Duration
|
||||
slotMaxTime: Duration
|
||||
showNonCurrentDates?: boolean
|
||||
dayCount?: number
|
||||
dateAlignment?: string
|
||||
dateIncrement?: Duration
|
||||
hiddenDays?: number[]
|
||||
weekends?: boolean
|
||||
nowInput?: DateInput | (() => DateInput)
|
||||
validRangeInput?: DateRangeInput | ((this: CalendarImpl, nowDate: Date) => DateRangeInput)
|
||||
visibleRangeInput?: DateRangeInput | ((this: CalendarImpl, nowDate: Date) => DateRangeInput)
|
||||
fixedWeekCount?: boolean
|
||||
}
|
||||
|
||||
export type DateProfileGeneratorClass = {
|
||||
new(props: DateProfileGeneratorProps): DateProfileGenerator
|
||||
}
|
||||
|
||||
export class DateProfileGenerator { // only publicly used for isHiddenDay :(
|
||||
nowDate: DateMarker
|
||||
|
||||
isHiddenDayHash: boolean[]
|
||||
|
||||
constructor(protected props: DateProfileGeneratorProps) {
|
||||
this.nowDate = getNow(props.nowInput, props.dateEnv)
|
||||
this.initHiddenDays()
|
||||
}
|
||||
|
||||
/* Date Range Computation
|
||||
------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Builds a structure with info about what the dates/ranges will be for the "prev" view.
|
||||
buildPrev(currentDateProfile: DateProfile, currentDate: DateMarker, forceToValid?: boolean): DateProfile {
|
||||
let { dateEnv } = this.props
|
||||
|
||||
let prevDate = dateEnv.subtract(
|
||||
dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
|
||||
currentDateProfile.dateIncrement,
|
||||
)
|
||||
|
||||
return this.build(prevDate, -1, forceToValid)
|
||||
}
|
||||
|
||||
// Builds a structure with info about what the dates/ranges will be for the "next" view.
|
||||
buildNext(currentDateProfile: DateProfile, currentDate: DateMarker, forceToValid?: boolean): DateProfile {
|
||||
let { dateEnv } = this.props
|
||||
|
||||
let nextDate = dateEnv.add(
|
||||
dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
|
||||
currentDateProfile.dateIncrement,
|
||||
)
|
||||
|
||||
return this.build(nextDate, 1, forceToValid)
|
||||
}
|
||||
|
||||
// Builds a structure holding dates/ranges for rendering around the given date.
|
||||
// Optional direction param indicates whether the date is being incremented/decremented
|
||||
// from its previous value. decremented = -1, incremented = 1 (default).
|
||||
build(currentDate: DateMarker, direction?, forceToValid = true): DateProfile {
|
||||
let { props } = this
|
||||
let validRange: DateRange
|
||||
let currentInfo
|
||||
let isRangeAllDay
|
||||
let renderRange: DateRange
|
||||
let activeRange: DateRange
|
||||
let isValid
|
||||
|
||||
validRange = this.buildValidRange()
|
||||
validRange = this.trimHiddenDays(validRange)
|
||||
|
||||
if (forceToValid) {
|
||||
currentDate = constrainMarkerToRange(currentDate, validRange)
|
||||
}
|
||||
|
||||
currentInfo = this.buildCurrentRangeInfo(currentDate, direction)
|
||||
isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit)
|
||||
renderRange = this.buildRenderRange(
|
||||
this.trimHiddenDays(currentInfo.range),
|
||||
currentInfo.unit,
|
||||
isRangeAllDay,
|
||||
)
|
||||
renderRange = this.trimHiddenDays(renderRange)
|
||||
activeRange = renderRange
|
||||
|
||||
if (!props.showNonCurrentDates) {
|
||||
activeRange = intersectRanges(activeRange, currentInfo.range)
|
||||
}
|
||||
|
||||
activeRange = this.adjustActiveRange(activeRange)
|
||||
activeRange = intersectRanges(activeRange, validRange) // might return null
|
||||
|
||||
// it's invalid if the originally requested date is not contained,
|
||||
// or if the range is completely outside of the valid range.
|
||||
isValid = rangesIntersect(currentInfo.range, validRange)
|
||||
|
||||
// HACK: constrain to render-range so `currentDate` is more useful to view rendering
|
||||
if (!rangeContainsMarker(renderRange, currentDate)) {
|
||||
currentDate = renderRange.start
|
||||
}
|
||||
|
||||
return {
|
||||
currentDate,
|
||||
|
||||
// constraint for where prev/next operations can go and where events can be dragged/resized to.
|
||||
// an object with optional start and end properties.
|
||||
validRange,
|
||||
|
||||
// range the view is formally responsible for.
|
||||
// for example, a month view might have 1st-31st, excluding padded dates
|
||||
currentRange: currentInfo.range,
|
||||
|
||||
// name of largest unit being displayed, like "month" or "week"
|
||||
currentRangeUnit: currentInfo.unit,
|
||||
|
||||
isRangeAllDay,
|
||||
|
||||
// dates that display events and accept drag-n-drop
|
||||
// will be `null` if no dates accept events
|
||||
activeRange,
|
||||
|
||||
// date range with a rendered skeleton
|
||||
// includes not-active days that need some sort of DOM
|
||||
renderRange,
|
||||
|
||||
// Duration object that denotes the first visible time of any given day
|
||||
slotMinTime: props.slotMinTime,
|
||||
|
||||
// Duration object that denotes the exclusive visible end time of any given day
|
||||
slotMaxTime: props.slotMaxTime,
|
||||
|
||||
isValid,
|
||||
|
||||
// how far the current date will move for a prev/next operation
|
||||
dateIncrement: this.buildDateIncrement(currentInfo.duration),
|
||||
// pass a fallback (might be null) ^
|
||||
}
|
||||
}
|
||||
|
||||
// Builds an object with optional start/end properties.
|
||||
// Indicates the minimum/maximum dates to display.
|
||||
// not responsible for trimming hidden days.
|
||||
buildValidRange(): OpenDateRange {
|
||||
let input = this.props.validRangeInput
|
||||
let simpleInput = typeof input === 'function'
|
||||
? input.call(this.props.calendarApi, this.nowDate)
|
||||
: input
|
||||
|
||||
return this.refineRange(simpleInput) ||
|
||||
{ start: null, end: null } // completely open-ended
|
||||
}
|
||||
|
||||
// Builds a structure with info about the "current" range, the range that is
|
||||
// highlighted as being the current month for example.
|
||||
// See build() for a description of `direction`.
|
||||
// Guaranteed to have `range` and `unit` properties. `duration` is optional.
|
||||
buildCurrentRangeInfo(date: DateMarker, direction) {
|
||||
let { props } = this
|
||||
let duration = null
|
||||
let unit = null
|
||||
let range = null
|
||||
let dayCount
|
||||
|
||||
if (props.duration) {
|
||||
duration = props.duration
|
||||
unit = props.durationUnit
|
||||
range = this.buildRangeFromDuration(date, direction, duration, unit)
|
||||
} else if ((dayCount = this.props.dayCount)) {
|
||||
unit = 'day'
|
||||
range = this.buildRangeFromDayCount(date, direction, dayCount)
|
||||
} else if ((range = this.buildCustomVisibleRange(date))) {
|
||||
unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit
|
||||
} else {
|
||||
duration = this.getFallbackDuration()
|
||||
unit = greatestDurationDenominator(duration).unit
|
||||
range = this.buildRangeFromDuration(date, direction, duration, unit)
|
||||
}
|
||||
|
||||
return { duration, unit, range }
|
||||
}
|
||||
|
||||
getFallbackDuration(): Duration {
|
||||
return createDuration({ day: 1 })
|
||||
}
|
||||
|
||||
// Returns a new activeRange to have time values (un-ambiguate)
|
||||
// slotMinTime or slotMaxTime causes the range to expand.
|
||||
adjustActiveRange(range: DateRange) {
|
||||
let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props
|
||||
let { start, end } = range
|
||||
|
||||
if (usesMinMaxTime) {
|
||||
// expand active range if slotMinTime is negative (why not when positive?)
|
||||
if (asRoughDays(slotMinTime) < 0) {
|
||||
start = startOfDay(start) // necessary?
|
||||
start = dateEnv.add(start, slotMinTime)
|
||||
}
|
||||
|
||||
// expand active range if slotMaxTime is beyond one day (why not when negative?)
|
||||
if (asRoughDays(slotMaxTime) > 1) {
|
||||
end = startOfDay(end) // necessary?
|
||||
end = addDays(end, -1)
|
||||
end = dateEnv.add(end, slotMaxTime)
|
||||
}
|
||||
}
|
||||
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
// Builds the "current" range when it is specified as an explicit duration.
|
||||
// `unit` is the already-computed greatestDurationDenominator unit of duration.
|
||||
buildRangeFromDuration(date: DateMarker, direction, duration: Duration, unit) {
|
||||
let { dateEnv, dateAlignment } = this.props
|
||||
let start: DateMarker
|
||||
let end: DateMarker
|
||||
let res
|
||||
|
||||
// compute what the alignment should be
|
||||
if (!dateAlignment) {
|
||||
let { dateIncrement } = this.props
|
||||
|
||||
if (dateIncrement) {
|
||||
// use the smaller of the two units
|
||||
if (asRoughMs(dateIncrement) < asRoughMs(duration)) {
|
||||
dateAlignment = greatestDurationDenominator(dateIncrement).unit
|
||||
} else {
|
||||
dateAlignment = unit
|
||||
}
|
||||
} else {
|
||||
dateAlignment = unit
|
||||
}
|
||||
}
|
||||
|
||||
// if the view displays a single day or smaller
|
||||
if (asRoughDays(duration) <= 1) {
|
||||
if (this.isHiddenDay(start)) {
|
||||
start = this.skipHiddenDays(start, direction)
|
||||
start = startOfDay(start)
|
||||
}
|
||||
}
|
||||
|
||||
function computeRes() {
|
||||
start = dateEnv.startOf(date, dateAlignment)
|
||||
end = dateEnv.add(start, duration)
|
||||
res = { start, end }
|
||||
}
|
||||
|
||||
computeRes()
|
||||
|
||||
// if range is completely enveloped by hidden days, go past the hidden days
|
||||
if (!this.trimHiddenDays(res)) {
|
||||
date = this.skipHiddenDays(date, direction)
|
||||
computeRes()
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Builds the "current" range when a dayCount is specified.
|
||||
buildRangeFromDayCount(date: DateMarker, direction, dayCount) {
|
||||
let { dateEnv, dateAlignment } = this.props
|
||||
let runningCount = 0
|
||||
let start: DateMarker = date
|
||||
let end: DateMarker
|
||||
|
||||
if (dateAlignment) {
|
||||
start = dateEnv.startOf(start, dateAlignment)
|
||||
}
|
||||
|
||||
start = startOfDay(start)
|
||||
start = this.skipHiddenDays(start, direction)
|
||||
|
||||
end = start
|
||||
do {
|
||||
end = addDays(end, 1)
|
||||
if (!this.isHiddenDay(end)) {
|
||||
runningCount += 1
|
||||
}
|
||||
} while (runningCount < dayCount)
|
||||
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
// Builds a normalized range object for the "visible" range,
|
||||
// which is a way to define the currentRange and activeRange at the same time.
|
||||
buildCustomVisibleRange(date: DateMarker) {
|
||||
let { props } = this
|
||||
let input = props.visibleRangeInput
|
||||
let simpleInput = typeof input === 'function'
|
||||
? input.call(props.calendarApi, props.dateEnv.toDate(date))
|
||||
: input
|
||||
|
||||
let range = this.refineRange(simpleInput)
|
||||
|
||||
if (range && (range.start == null || range.end == null)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return range
|
||||
}
|
||||
|
||||
// Computes the range that will represent the element/cells for *rendering*,
|
||||
// but which may have voided days/times.
|
||||
// not responsible for trimming hidden days.
|
||||
buildRenderRange(currentRange: DateRange, currentRangeUnit, isRangeAllDay) {
|
||||
return currentRange
|
||||
}
|
||||
|
||||
// Compute the duration value that should be added/substracted to the current date
|
||||
// when a prev/next operation happens.
|
||||
buildDateIncrement(fallback): Duration {
|
||||
let { dateIncrement } = this.props
|
||||
let customAlignment
|
||||
|
||||
if (dateIncrement) {
|
||||
return dateIncrement
|
||||
}
|
||||
|
||||
if ((customAlignment = this.props.dateAlignment)) {
|
||||
return createDuration(1, customAlignment)
|
||||
}
|
||||
|
||||
if (fallback) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return createDuration({ days: 1 })
|
||||
}
|
||||
|
||||
refineRange(rangeInput: DateRangeInput | undefined): DateRange | null {
|
||||
if (rangeInput) {
|
||||
let range = parseRange(rangeInput, this.props.dateEnv)
|
||||
|
||||
if (range) {
|
||||
range = computeVisibleDayRange(range)
|
||||
}
|
||||
|
||||
return range
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/* Hidden Days
|
||||
------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Initializes internal variables related to calculating hidden days-of-week
|
||||
initHiddenDays() {
|
||||
let hiddenDays = this.props.hiddenDays || [] // array of day-of-week indices that are hidden
|
||||
let isHiddenDayHash = [] // is the day-of-week hidden? (hash with day-of-week-index -> bool)
|
||||
let dayCnt = 0
|
||||
let i
|
||||
|
||||
if (this.props.weekends === false) {
|
||||
hiddenDays.push(0, 6) // 0=sunday, 6=saturday
|
||||
}
|
||||
|
||||
for (i = 0; i < 7; i += 1) {
|
||||
if (
|
||||
!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)
|
||||
) {
|
||||
dayCnt += 1
|
||||
}
|
||||
}
|
||||
|
||||
if (!dayCnt) {
|
||||
throw new Error('invalid hiddenDays') // all days were hidden? bad.
|
||||
}
|
||||
|
||||
this.isHiddenDayHash = isHiddenDayHash
|
||||
}
|
||||
|
||||
// Remove days from the beginning and end of the range that are computed as hidden.
|
||||
// If the whole range is trimmed off, returns null
|
||||
trimHiddenDays(range: DateRange): DateRange | null {
|
||||
let { start, end } = range
|
||||
|
||||
if (start) {
|
||||
start = this.skipHiddenDays(start)
|
||||
}
|
||||
|
||||
if (end) {
|
||||
end = this.skipHiddenDays(end, -1, true)
|
||||
}
|
||||
|
||||
if (start == null || end == null || start < end) {
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Is the current day hidden?
|
||||
// `day` is a day-of-week index (0-6), or a Date (used for UTC)
|
||||
isHiddenDay(day) {
|
||||
if (day instanceof Date) {
|
||||
day = day.getUTCDay()
|
||||
}
|
||||
return this.isHiddenDayHash[day]
|
||||
}
|
||||
|
||||
// Incrementing the current day until it is no longer a hidden day, returning a copy.
|
||||
// DOES NOT CONSIDER validRange!
|
||||
// If the initial value of `date` is not a hidden day, don't do anything.
|
||||
// Pass `isExclusive` as `true` if you are dealing with an end date.
|
||||
// `inc` defaults to `1` (increment one day forward each time)
|
||||
skipHiddenDays(date: DateMarker, inc = 1, isExclusive = false) {
|
||||
while (
|
||||
this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]
|
||||
) {
|
||||
date = addDays(date, inc)
|
||||
}
|
||||
return date
|
||||
}
|
||||
}
|
94
fullcalendar-main/packages/core/src/NowTimer.ts
Normal file
94
fullcalendar-main/packages/core/src/NowTimer.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { DateMarker, addMs, startOfDay, addDays } from './datelib/marker.js'
|
||||
import { createDuration } from './datelib/duration.js'
|
||||
import { ViewContext, ViewContextType } from './ViewContext.js'
|
||||
import { ComponentChildren, Component } from './preact.js'
|
||||
import { DateRange } from './datelib/date-range.js'
|
||||
import { getNow } from './reducers/current-date.js'
|
||||
|
||||
export interface NowTimerProps {
|
||||
unit: string // TODO: add type of unit
|
||||
children: (now: DateMarker, todayRange: DateRange) => ComponentChildren
|
||||
}
|
||||
|
||||
interface NowTimerState {
|
||||
nowDate: DateMarker
|
||||
todayRange: DateRange
|
||||
}
|
||||
|
||||
export class NowTimer extends Component<NowTimerProps, NowTimerState> {
|
||||
static contextType: any = ViewContextType
|
||||
context: ViewContext // do this for all components that use the context!!!
|
||||
initialNowDate: DateMarker
|
||||
initialNowQueriedMs: number
|
||||
timeoutId: any
|
||||
|
||||
constructor(props: NowTimerProps, context: ViewContext) {
|
||||
super(props, context)
|
||||
|
||||
this.initialNowDate = getNow(context.options.now, context.dateEnv)
|
||||
this.initialNowQueriedMs = new Date().valueOf()
|
||||
|
||||
this.state = this.computeTiming().currentState
|
||||
}
|
||||
|
||||
render() {
|
||||
let { props, state } = this
|
||||
return props.children(state.nowDate, state.todayRange)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setTimeout()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: NowTimerProps) {
|
||||
if (prevProps.unit !== this.props.unit) {
|
||||
this.clearTimeout()
|
||||
this.setTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearTimeout()
|
||||
}
|
||||
|
||||
private computeTiming() {
|
||||
let { props, context } = this
|
||||
let unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs)
|
||||
let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit)
|
||||
let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit))
|
||||
let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf()
|
||||
|
||||
// there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342)
|
||||
// ensure no longer than a day
|
||||
waitMs = Math.min(1000 * 60 * 60 * 24, waitMs)
|
||||
|
||||
return {
|
||||
currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) } as NowTimerState,
|
||||
nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) } as NowTimerState,
|
||||
waitMs,
|
||||
}
|
||||
}
|
||||
|
||||
private setTimeout() {
|
||||
let { nextState, waitMs } = this.computeTiming()
|
||||
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.setState(nextState, () => {
|
||||
this.setTimeout()
|
||||
})
|
||||
}, waitMs)
|
||||
}
|
||||
|
||||
private clearTimeout() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDayRange(date: DateMarker): DateRange { // TODO: make this a general util
|
||||
let start = startOfDay(date)
|
||||
let end = addDays(start, 1)
|
||||
|
||||
return { start, end }
|
||||
}
|
53
fullcalendar-main/packages/core/src/ScrollResponder.ts
Normal file
53
fullcalendar-main/packages/core/src/ScrollResponder.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Duration } from './datelib/duration.js'
|
||||
import { Emitter } from './common/Emitter.js'
|
||||
import { CalendarListeners } from './options.js'
|
||||
|
||||
export interface ScrollRequest {
|
||||
time?: Duration
|
||||
[otherProp: string]: any
|
||||
}
|
||||
|
||||
export type ScrollRequestHandler = (request: ScrollRequest) => boolean
|
||||
|
||||
export class ScrollResponder {
|
||||
queuedRequest: ScrollRequest
|
||||
|
||||
constructor(
|
||||
private execFunc: ScrollRequestHandler,
|
||||
private emitter: Emitter<CalendarListeners>,
|
||||
private scrollTime: Duration,
|
||||
private scrollTimeReset: boolean,
|
||||
) {
|
||||
emitter.on('_scrollRequest', this.handleScrollRequest)
|
||||
this.fireInitialScroll()
|
||||
}
|
||||
|
||||
detach() {
|
||||
this.emitter.off('_scrollRequest', this.handleScrollRequest)
|
||||
}
|
||||
|
||||
update(isDatesNew: boolean) {
|
||||
if (isDatesNew && this.scrollTimeReset) {
|
||||
this.fireInitialScroll() // will drain
|
||||
} else {
|
||||
this.drain()
|
||||
}
|
||||
}
|
||||
|
||||
private fireInitialScroll() {
|
||||
this.handleScrollRequest({
|
||||
time: this.scrollTime,
|
||||
})
|
||||
}
|
||||
|
||||
private handleScrollRequest = (request: ScrollRequest) => {
|
||||
this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request)
|
||||
this.drain()
|
||||
}
|
||||
|
||||
private drain() {
|
||||
if (this.queuedRequest && this.execFunc(this.queuedRequest)) {
|
||||
this.queuedRequest = null
|
||||
}
|
||||
}
|
||||
}
|
67
fullcalendar-main/packages/core/src/Toolbar.tsx
Normal file
67
fullcalendar-main/packages/core/src/Toolbar.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { createElement } from './preact.js'
|
||||
import { BaseComponent } from './vdom-util.js'
|
||||
import { ToolbarModel, ToolbarWidget } from './toolbar-struct.js'
|
||||
import { ToolbarSection, ToolbarContent } from './ToolbarSection.js'
|
||||
|
||||
export interface ToolbarProps extends ToolbarContent {
|
||||
extraClassName: string // wish this could be array, but easier for pureness
|
||||
model: ToolbarModel
|
||||
titleId: string
|
||||
}
|
||||
|
||||
export class Toolbar extends BaseComponent<ToolbarProps> {
|
||||
render() {
|
||||
let { model, extraClassName } = this.props
|
||||
let forceLtr = false
|
||||
let startContent
|
||||
let endContent
|
||||
let sectionWidgets = model.sectionWidgets
|
||||
let centerContent = sectionWidgets.center
|
||||
|
||||
if (sectionWidgets.left) {
|
||||
forceLtr = true
|
||||
startContent = sectionWidgets.left
|
||||
} else {
|
||||
startContent = sectionWidgets.start
|
||||
}
|
||||
|
||||
if (sectionWidgets.right) {
|
||||
forceLtr = true
|
||||
endContent = sectionWidgets.right
|
||||
} else {
|
||||
endContent = sectionWidgets.end
|
||||
}
|
||||
|
||||
let classNames = [
|
||||
extraClassName || '',
|
||||
'fc-toolbar',
|
||||
forceLtr ? 'fc-toolbar-ltr' : '',
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={classNames.join(' ')}>
|
||||
{this.renderSection('start', startContent || [])}
|
||||
{this.renderSection('center', centerContent || [])}
|
||||
{this.renderSection('end', endContent || [])}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderSection(key: string, widgetGroups: ToolbarWidget[][]) {
|
||||
let { props } = this
|
||||
|
||||
return (
|
||||
<ToolbarSection
|
||||
key={key}
|
||||
widgetGroups={widgetGroups}
|
||||
title={props.title}
|
||||
navUnit={props.navUnit}
|
||||
activeButton={props.activeButton}
|
||||
isTodayEnabled={props.isTodayEnabled}
|
||||
isPrevEnabled={props.isPrevEnabled}
|
||||
isNextEnabled={props.isNextEnabled}
|
||||
titleId={props.titleId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
74
fullcalendar-main/packages/core/src/ToolbarSection.tsx
Normal file
74
fullcalendar-main/packages/core/src/ToolbarSection.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { createElement, VNode } from './preact.js'
|
||||
import { BaseComponent } from './vdom-util.js'
|
||||
import { ToolbarWidget } from './toolbar-struct.js'
|
||||
|
||||
export interface ToolbarContent {
|
||||
title: string
|
||||
titleId: string
|
||||
navUnit: string
|
||||
activeButton: string
|
||||
isTodayEnabled: boolean
|
||||
isPrevEnabled: boolean
|
||||
isNextEnabled: boolean
|
||||
}
|
||||
|
||||
export interface ToolbarSectionProps extends ToolbarContent {
|
||||
widgetGroups: ToolbarWidget[][]
|
||||
}
|
||||
|
||||
export class ToolbarSection extends BaseComponent<ToolbarSectionProps> {
|
||||
render(): any {
|
||||
let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup))
|
||||
|
||||
return createElement('div', { className: 'fc-toolbar-chunk' }, ...children)
|
||||
}
|
||||
|
||||
renderWidgetGroup(widgetGroup: ToolbarWidget[]): any {
|
||||
let { props } = this
|
||||
let { theme } = this.context
|
||||
let children: VNode[] = []
|
||||
let isOnlyButtons = true
|
||||
|
||||
for (let widget of widgetGroup) {
|
||||
let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget
|
||||
|
||||
if (buttonName === 'title') {
|
||||
isOnlyButtons = false
|
||||
children.push(
|
||||
<h2 className="fc-toolbar-title" id={props.titleId}>{props.title}</h2>,
|
||||
)
|
||||
} else {
|
||||
let isPressed = buttonName === props.activeButton
|
||||
let isDisabled =
|
||||
(!props.isTodayEnabled && buttonName === 'today') ||
|
||||
(!props.isPrevEnabled && buttonName === 'prev') ||
|
||||
(!props.isNextEnabled && buttonName === 'next')
|
||||
|
||||
let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')]
|
||||
if (isPressed) {
|
||||
buttonClasses.push(theme.getClass('buttonActive'))
|
||||
}
|
||||
|
||||
children.push(
|
||||
<button
|
||||
type="button"
|
||||
title={typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint}
|
||||
disabled={isDisabled}
|
||||
aria-pressed={isPressed}
|
||||
className={buttonClasses.join(' ')}
|
||||
onClick={buttonClick}
|
||||
>
|
||||
{buttonText || (buttonIcon ? <span className={buttonIcon} role="img" /> : '')}
|
||||
</button>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (children.length > 1) {
|
||||
let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''
|
||||
|
||||
return createElement('div', { className: groupClassName }, ...children)
|
||||
}
|
||||
return children[0]
|
||||
}
|
||||
}
|
38
fullcalendar-main/packages/core/src/View.ts
Normal file
38
fullcalendar-main/packages/core/src/View.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { DateProfile } from './DateProfileGenerator.js'
|
||||
import { EventStore } from './structs/event-store.js'
|
||||
import { EventUiHash } from './component/event-ui.js'
|
||||
import { sliceEventStore, EventRenderRange } from './component/event-rendering.js'
|
||||
import { DateSpan } from './structs/date-span.js'
|
||||
import { EventInteractionState } from './interactions/event-interaction-state.js'
|
||||
import { Duration } from './datelib/duration.js'
|
||||
|
||||
export interface ViewProps {
|
||||
dateProfile: DateProfile
|
||||
businessHours: EventStore
|
||||
eventStore: EventStore
|
||||
eventUiBases: EventUiHash
|
||||
dateSelection: DateSpan | null
|
||||
eventSelection: string
|
||||
eventDrag: EventInteractionState | null
|
||||
eventResize: EventInteractionState | null
|
||||
isHeightAuto: boolean
|
||||
forPrint: boolean
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
|
||||
/*
|
||||
if nextDayThreshold is specified, slicing is done in an all-day fashion.
|
||||
you can get nextDayThreshold from context.nextDayThreshold
|
||||
*/
|
||||
export function sliceEvents(
|
||||
props: ViewProps & { dateProfile: DateProfile, nextDayThreshold: Duration },
|
||||
allDay?: boolean,
|
||||
): EventRenderRange[] {
|
||||
return sliceEventStore(
|
||||
props.eventStore,
|
||||
props.eventUiBases,
|
||||
props.dateProfile.activeRange,
|
||||
allDay ? props.nextDayThreshold : null,
|
||||
).fg
|
||||
}
|
85
fullcalendar-main/packages/core/src/ViewContext.ts
Normal file
85
fullcalendar-main/packages/core/src/ViewContext.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { CalendarImpl } from './api/CalendarImpl.js'
|
||||
import { ViewImpl } from './api/ViewImpl.js'
|
||||
import { Theme } from './theme/Theme.js'
|
||||
import { DateEnv } from './datelib/env.js'
|
||||
import { PluginHooks } from './plugin-system-struct.js'
|
||||
import { createContext, Context } from './preact.js'
|
||||
import { ScrollResponder, ScrollRequestHandler } from './ScrollResponder.js'
|
||||
import { DateProfileGenerator } from './DateProfileGenerator.js'
|
||||
import { ViewSpec } from './structs/view-spec.js'
|
||||
import { CalendarData } from './reducers/data-types.js'
|
||||
import { Action } from './reducers/Action.js'
|
||||
import { Emitter } from './common/Emitter.js'
|
||||
import { InteractionSettingsInput } from './interactions/interaction.js'
|
||||
import { DateComponent } from './component/DateComponent.js'
|
||||
import { CalendarContext } from './CalendarContext.js'
|
||||
import { createDuration } from './datelib/duration.js'
|
||||
import { ViewOptionsRefined, CalendarListeners } from './options.js'
|
||||
|
||||
export const ViewContextType: Context<any> = createContext<ViewContext>({} as any) // for Components
|
||||
export type ResizeHandler = (force: boolean) => void
|
||||
|
||||
/*
|
||||
it's important that ViewContext extends CalendarContext so that components that subscribe to ViewContext
|
||||
can pass in their ViewContext to util functions that accept CalendarContext.
|
||||
*/
|
||||
export interface ViewContext extends CalendarContext {
|
||||
options: ViewOptionsRefined // more specific than BaseOptionsRefined
|
||||
theme: Theme
|
||||
isRtl: boolean
|
||||
dateProfileGenerator: DateProfileGenerator
|
||||
viewSpec: ViewSpec
|
||||
viewApi: ViewImpl
|
||||
addResizeHandler: (handler: ResizeHandler) => void
|
||||
removeResizeHandler: (handler: ResizeHandler) => void
|
||||
createScrollResponder: (execFunc: ScrollRequestHandler) => ScrollResponder
|
||||
registerInteractiveComponent: (component: DateComponent<any>, settingsInput: InteractionSettingsInput) => void
|
||||
unregisterInteractiveComponent: (component: DateComponent<any>) => void
|
||||
}
|
||||
|
||||
export function buildViewContext(
|
||||
viewSpec: ViewSpec,
|
||||
viewApi: ViewImpl,
|
||||
viewOptions: ViewOptionsRefined,
|
||||
dateProfileGenerator: DateProfileGenerator,
|
||||
dateEnv: DateEnv,
|
||||
theme: Theme,
|
||||
pluginHooks: PluginHooks,
|
||||
dispatch: (action: Action) => void,
|
||||
getCurrentData: () => CalendarData,
|
||||
emitter: Emitter<CalendarListeners>,
|
||||
calendarApi: CalendarImpl,
|
||||
registerInteractiveComponent: (component: DateComponent<any>, settingsInput: InteractionSettingsInput) => void,
|
||||
unregisterInteractiveComponent: (component: DateComponent<any>) => void,
|
||||
): ViewContext {
|
||||
return {
|
||||
dateEnv,
|
||||
options: viewOptions,
|
||||
pluginHooks,
|
||||
emitter,
|
||||
dispatch,
|
||||
getCurrentData,
|
||||
calendarApi,
|
||||
viewSpec,
|
||||
viewApi,
|
||||
dateProfileGenerator,
|
||||
theme,
|
||||
isRtl: viewOptions.direction === 'rtl',
|
||||
addResizeHandler(handler: ResizeHandler) {
|
||||
emitter.on('_resize', handler)
|
||||
},
|
||||
removeResizeHandler(handler: ResizeHandler) {
|
||||
emitter.off('_resize', handler)
|
||||
},
|
||||
createScrollResponder(execFunc: ScrollRequestHandler) {
|
||||
return new ScrollResponder(
|
||||
execFunc,
|
||||
emitter,
|
||||
createDuration(viewOptions.scrollTime),
|
||||
viewOptions.scrollTimeReset,
|
||||
)
|
||||
},
|
||||
registerInteractiveComponent,
|
||||
unregisterInteractiveComponent,
|
||||
}
|
||||
}
|
90
fullcalendar-main/packages/core/src/ViewHarness.tsx
Normal file
90
fullcalendar-main/packages/core/src/ViewHarness.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { BaseComponent, setRef } from './vdom-util.js'
|
||||
import { ComponentChildren, Ref, createElement } from './preact.js'
|
||||
import { CssDimValue } from './scrollgrid/util.js'
|
||||
|
||||
export interface ViewHarnessProps {
|
||||
elRef?: Ref<HTMLDivElement>
|
||||
labeledById?: string
|
||||
liquid?: boolean
|
||||
height?: CssDimValue
|
||||
aspectRatio?: number
|
||||
children?: ComponentChildren
|
||||
}
|
||||
|
||||
interface ViewHarnessState {
|
||||
availableWidth: number | null
|
||||
}
|
||||
|
||||
export class ViewHarness extends BaseComponent<ViewHarnessProps, ViewHarnessState> {
|
||||
el: HTMLElement
|
||||
|
||||
state: ViewHarnessState = {
|
||||
availableWidth: null,
|
||||
}
|
||||
|
||||
render() {
|
||||
let { props, state } = this
|
||||
let { aspectRatio } = props
|
||||
|
||||
let classNames = [
|
||||
'fc-view-harness',
|
||||
(aspectRatio || props.liquid || props.height)
|
||||
? 'fc-view-harness-active' // harness controls the height
|
||||
: 'fc-view-harness-passive', // let the view do the height
|
||||
]
|
||||
let height: CssDimValue = ''
|
||||
let paddingBottom: CssDimValue = ''
|
||||
|
||||
if (aspectRatio) {
|
||||
if (state.availableWidth !== null) {
|
||||
height = state.availableWidth / aspectRatio
|
||||
} else {
|
||||
// while waiting to know availableWidth, we can't set height to *zero*
|
||||
// because will cause lots of unnecessary scrollbars within scrollgrid.
|
||||
// BETTER: don't start rendering ANYTHING yet until we know container width
|
||||
// NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
|
||||
paddingBottom = `${(1 / aspectRatio) * 100}%`
|
||||
}
|
||||
} else {
|
||||
height = props.height || ''
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-labelledby={props.labeledById}
|
||||
ref={this.handleEl}
|
||||
className={classNames.join(' ')}
|
||||
style={{ height, paddingBottom }}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.context.addResizeHandler(this.handleResize)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.removeResizeHandler(this.handleResize)
|
||||
}
|
||||
|
||||
handleEl = (el: HTMLElement | null) => {
|
||||
this.el = el
|
||||
setRef(this.props.elRef, el)
|
||||
this.updateAvailableWidth()
|
||||
}
|
||||
|
||||
handleResize = () => {
|
||||
this.updateAvailableWidth()
|
||||
}
|
||||
|
||||
updateAvailableWidth() {
|
||||
if (
|
||||
this.el && // needed. but why?
|
||||
this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
|
||||
) {
|
||||
this.setState({ availableWidth: this.el.offsetWidth })
|
||||
}
|
||||
}
|
||||
}
|
85
fullcalendar-main/packages/core/src/api/CalendarApi.ts
Normal file
85
fullcalendar-main/packages/core/src/api/CalendarApi.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { ViewApi } from './ViewApi.js'
|
||||
import { EventSourceApi } from './EventSourceApi.js'
|
||||
import { EventApi } from './EventApi.js'
|
||||
import {
|
||||
CalendarOptions,
|
||||
CalendarListeners,
|
||||
DateInput,
|
||||
DurationInput,
|
||||
DateRangeInput,
|
||||
EventSourceInput,
|
||||
EventInput,
|
||||
FormatterInput,
|
||||
} from './structs.js'
|
||||
|
||||
export interface CalendarApi {
|
||||
view: ViewApi
|
||||
updateSize(): void
|
||||
|
||||
// Options
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
setOption<OptionName extends keyof CalendarOptions>(name: OptionName, val: CalendarOptions[OptionName]): void
|
||||
getOption<OptionName extends keyof CalendarOptions>(name: OptionName): CalendarOptions[OptionName]
|
||||
getAvailableLocaleCodes(): string[]
|
||||
|
||||
// Trigger
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
on<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, handler: CalendarListeners[ListenerName]): void
|
||||
off<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, handler: CalendarListeners[ListenerName]): void
|
||||
trigger<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, ...args: Parameters<CalendarListeners[ListenerName]>): void
|
||||
|
||||
// View
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
changeView(viewType: string, dateOrRange?: DateRangeInput | DateInput): void
|
||||
zoomTo(dateMarker: Date, viewType?: string): void
|
||||
|
||||
// Current Date
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
prev(): void
|
||||
next(): void
|
||||
prevYear(): void
|
||||
nextYear(): void
|
||||
today(): void
|
||||
gotoDate(zonedDateInput: DateInput): void
|
||||
incrementDate(deltaInput: DurationInput): void
|
||||
getDate(): Date
|
||||
|
||||
// Date Formatting Utils
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
formatDate(d: DateInput, formatter: FormatterInput): string
|
||||
formatRange(d0: DateInput, d1: DateInput, settings: any): string // TODO: settings type
|
||||
formatIso(d: DateInput, omitTime?: boolean): string
|
||||
|
||||
// Date Selection / Event Selection / DayClick
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
select(dateOrObj: DateInput | any, endDate?: DateInput): void
|
||||
unselect(): void
|
||||
|
||||
// Public Events API
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
addEvent(eventInput: EventInput, sourceInput?: EventSourceApi | string | boolean): EventApi | null
|
||||
getEventById(id: string): EventApi | null
|
||||
getEvents(): EventApi[]
|
||||
removeAllEvents(): void
|
||||
|
||||
// Public Event Sources API
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
getEventSources(): EventSourceApi[]
|
||||
getEventSourceById(id: string): EventSourceApi | null
|
||||
addEventSource(sourceInput: EventSourceInput): EventSourceApi
|
||||
removeAllEventSources(): void
|
||||
refetchEvents(): void
|
||||
|
||||
// Scroll
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
scrollToTime(timeInput: DurationInput): void
|
||||
}
|
511
fullcalendar-main/packages/core/src/api/CalendarImpl.ts
Normal file
511
fullcalendar-main/packages/core/src/api/CalendarImpl.ts
Normal file
|
@ -0,0 +1,511 @@
|
|||
import { createFormatter, FormatterInput } from '../datelib/formatting.js'
|
||||
import { createDuration } from '../datelib/duration.js'
|
||||
import { parseDateSpan } from '../structs/date-span.js'
|
||||
import { parseEventSource } from '../structs/event-source-parse.js'
|
||||
import { parseEvent } from '../structs/event-parse.js'
|
||||
import { eventTupleToStore } from '../structs/event-store.js'
|
||||
import { ViewSpec } from '../structs/view-spec.js'
|
||||
import { PointerDragEvent } from '../interactions/pointer.js'
|
||||
import { getNow } from '../reducers/current-date.js'
|
||||
import { triggerDateSelect, triggerDateUnselect } from '../calendar-utils.js'
|
||||
import { hashValuesToArray } from '../util/object.js'
|
||||
import { CalendarDataManager } from '../reducers/CalendarDataManager.js'
|
||||
import { Action } from '../reducers/Action.js'
|
||||
import { EventSource } from '../structs/event-source.js'
|
||||
import { eventApiToStore, buildEventApis, EventImpl } from './EventImpl.js'
|
||||
import { CalendarData } from '../reducers/data-types.js'
|
||||
import { CalendarApi } from './CalendarApi.js'
|
||||
import { ViewImpl } from './ViewImpl.js'
|
||||
import { EventSourceImpl } from './EventSourceImpl.js'
|
||||
import {
|
||||
CalendarOptions,
|
||||
CalendarListeners,
|
||||
DateInput,
|
||||
DurationInput,
|
||||
DateSpanInput,
|
||||
DateRangeInput,
|
||||
EventSourceInput,
|
||||
EventInput,
|
||||
} from './structs.js'
|
||||
|
||||
export class CalendarImpl implements CalendarApi {
|
||||
currentDataManager?: CalendarDataManager // will be set by CalendarDataManager
|
||||
|
||||
getCurrentData(): CalendarData {
|
||||
return this.currentDataManager!.getCurrentData()
|
||||
}
|
||||
|
||||
dispatch(action: Action): void {
|
||||
this.currentDataManager!.dispatch(action)
|
||||
}
|
||||
|
||||
get view(): ViewImpl { return this.getCurrentData().viewApi }
|
||||
|
||||
batchRendering(callback: () => void): void { // subclasses should implement
|
||||
callback()
|
||||
}
|
||||
|
||||
updateSize(): void {
|
||||
this.trigger('_resize', true)
|
||||
}
|
||||
|
||||
// Options
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
setOption<OptionName extends keyof CalendarOptions>(name: OptionName, val: CalendarOptions[OptionName]): void {
|
||||
this.dispatch({
|
||||
type: 'SET_OPTION',
|
||||
optionName: name,
|
||||
rawOptionValue: val,
|
||||
})
|
||||
}
|
||||
|
||||
getOption<OptionName extends keyof CalendarOptions>(name: OptionName): CalendarOptions[OptionName] {
|
||||
return this.currentDataManager!.currentCalendarOptionsInput[name]
|
||||
}
|
||||
|
||||
getAvailableLocaleCodes(): string[] {
|
||||
return Object.keys(this.getCurrentData().availableRawLocales)
|
||||
}
|
||||
|
||||
// Trigger
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
on<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, handler: CalendarListeners[ListenerName]): void {
|
||||
let { currentDataManager } = this
|
||||
|
||||
if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
|
||||
currentDataManager.emitter.on(handlerName, handler)
|
||||
} else {
|
||||
console.warn(`Unknown listener name '${handlerName}'`)
|
||||
}
|
||||
}
|
||||
|
||||
off<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, handler: CalendarListeners[ListenerName]): void {
|
||||
this.currentDataManager!.emitter.off(handlerName, handler)
|
||||
}
|
||||
|
||||
// not meant for public use
|
||||
trigger<ListenerName extends keyof CalendarListeners>(handlerName: ListenerName, ...args: Parameters<CalendarListeners[ListenerName]>): void {
|
||||
this.currentDataManager!.emitter.trigger(handlerName, ...args)
|
||||
}
|
||||
|
||||
// View
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
changeView(viewType: string, dateOrRange?: DateRangeInput | DateInput): void {
|
||||
this.batchRendering(() => {
|
||||
this.unselect()
|
||||
|
||||
if (dateOrRange) {
|
||||
if ((dateOrRange as DateRangeInput).start && (dateOrRange as DateRangeInput).end) { // a range
|
||||
this.dispatch({
|
||||
type: 'CHANGE_VIEW_TYPE',
|
||||
viewType,
|
||||
})
|
||||
this.dispatch({ // not very efficient to do two dispatches
|
||||
type: 'SET_OPTION',
|
||||
optionName: 'visibleRange',
|
||||
rawOptionValue: dateOrRange,
|
||||
})
|
||||
} else {
|
||||
let { dateEnv } = this.getCurrentData()
|
||||
|
||||
this.dispatch({
|
||||
type: 'CHANGE_VIEW_TYPE',
|
||||
viewType,
|
||||
dateMarker: dateEnv.createMarker(dateOrRange as DateInput),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.dispatch({
|
||||
type: 'CHANGE_VIEW_TYPE',
|
||||
viewType,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Forces navigation to a view for the given date.
|
||||
// `viewType` can be a specific view name or a generic one like "week" or "day".
|
||||
// needs to change
|
||||
zoomTo(dateMarker: Date, viewType?: string): void {
|
||||
let state = this.getCurrentData()
|
||||
let spec
|
||||
|
||||
viewType = viewType || 'day' // day is default zoom
|
||||
spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType)
|
||||
|
||||
this.unselect()
|
||||
|
||||
if (spec) {
|
||||
this.dispatch({
|
||||
type: 'CHANGE_VIEW_TYPE',
|
||||
viewType: spec.type,
|
||||
dateMarker,
|
||||
})
|
||||
} else {
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Given a duration singular unit, like "week" or "day", finds a matching view spec.
|
||||
// Preference is given to views that have corresponding buttons.
|
||||
private getUnitViewSpec(unit: string): ViewSpec | null {
|
||||
let { viewSpecs, toolbarConfig } = this.getCurrentData()
|
||||
let viewTypes = [].concat(
|
||||
toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [],
|
||||
toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : [],
|
||||
)
|
||||
let i
|
||||
let spec
|
||||
|
||||
for (let viewType in viewSpecs) {
|
||||
viewTypes.push(viewType)
|
||||
}
|
||||
|
||||
for (i = 0; i < viewTypes.length; i += 1) {
|
||||
spec = viewSpecs[viewTypes[i]]
|
||||
if (spec) {
|
||||
if (spec.singleUnit === unit) {
|
||||
return spec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Current Date
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
prev(): void {
|
||||
this.unselect()
|
||||
this.dispatch({ type: 'PREV' })
|
||||
}
|
||||
|
||||
next(): void {
|
||||
this.unselect()
|
||||
this.dispatch({ type: 'NEXT' })
|
||||
}
|
||||
|
||||
prevYear(): void {
|
||||
let state = this.getCurrentData()
|
||||
this.unselect()
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker: state.dateEnv.addYears(state.currentDate, -1),
|
||||
})
|
||||
}
|
||||
|
||||
nextYear(): void {
|
||||
let state = this.getCurrentData()
|
||||
|
||||
this.unselect()
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker: state.dateEnv.addYears(state.currentDate, 1),
|
||||
})
|
||||
}
|
||||
|
||||
today(): void {
|
||||
let state = this.getCurrentData()
|
||||
|
||||
this.unselect()
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker: getNow(state.calendarOptions.now, state.dateEnv),
|
||||
})
|
||||
}
|
||||
|
||||
gotoDate(zonedDateInput: DateInput): void {
|
||||
let state = this.getCurrentData()
|
||||
|
||||
this.unselect()
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker: state.dateEnv.createMarker(zonedDateInput),
|
||||
})
|
||||
}
|
||||
|
||||
incrementDate(deltaInput: DurationInput): void {
|
||||
let state = this.getCurrentData()
|
||||
let delta = createDuration(deltaInput)
|
||||
|
||||
if (delta) { // else, warn about invalid input?
|
||||
this.unselect()
|
||||
this.dispatch({
|
||||
type: 'CHANGE_DATE',
|
||||
dateMarker: state.dateEnv.add(state.currentDate, delta),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getDate(): Date {
|
||||
let state = this.getCurrentData()
|
||||
return state.dateEnv.toDate(state.currentDate)
|
||||
}
|
||||
|
||||
// Date Formatting Utils
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
formatDate(d: DateInput, formatter: FormatterInput): string {
|
||||
let { dateEnv } = this.getCurrentData()
|
||||
|
||||
return dateEnv.format(
|
||||
dateEnv.createMarker(d),
|
||||
createFormatter(formatter),
|
||||
)
|
||||
}
|
||||
|
||||
// `settings` is for formatter AND isEndExclusive
|
||||
formatRange(d0: DateInput, d1: DateInput, settings: any): string { // TODO: settings type
|
||||
let { dateEnv } = this.getCurrentData()
|
||||
|
||||
return dateEnv.formatRange(
|
||||
dateEnv.createMarker(d0),
|
||||
dateEnv.createMarker(d1),
|
||||
createFormatter(settings),
|
||||
settings,
|
||||
)
|
||||
}
|
||||
|
||||
formatIso(d: DateInput, omitTime?: boolean): string {
|
||||
let { dateEnv } = this.getCurrentData()
|
||||
|
||||
return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime })
|
||||
}
|
||||
|
||||
// Date Selection / Event Selection / DayClick
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
select(dateOrObj: DateInput | any, endDate?: DateInput): void {
|
||||
let selectionInput: DateSpanInput
|
||||
|
||||
if (endDate == null) {
|
||||
if (dateOrObj.start != null) {
|
||||
selectionInput = dateOrObj as DateSpanInput
|
||||
} else {
|
||||
selectionInput = {
|
||||
start: dateOrObj,
|
||||
end: null,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectionInput = {
|
||||
start: dateOrObj,
|
||||
end: endDate,
|
||||
} as DateSpanInput
|
||||
}
|
||||
|
||||
let state = this.getCurrentData()
|
||||
let selection = parseDateSpan(
|
||||
selectionInput,
|
||||
state.dateEnv,
|
||||
createDuration({ days: 1 }), // TODO: cache this?
|
||||
)
|
||||
|
||||
if (selection) { // throw parse error otherwise?
|
||||
this.dispatch({ type: 'SELECT_DATES', selection })
|
||||
triggerDateSelect(selection, null, state)
|
||||
}
|
||||
}
|
||||
|
||||
unselect(pev?: PointerDragEvent): void {
|
||||
let state = this.getCurrentData()
|
||||
|
||||
if (state.dateSelection) {
|
||||
this.dispatch({ type: 'UNSELECT_DATES' })
|
||||
triggerDateUnselect(pev, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Public Events API
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
addEvent(eventInput: EventInput, sourceInput?: EventSourceImpl | string | boolean): EventImpl | null {
|
||||
if (eventInput instanceof EventImpl) {
|
||||
let def = eventInput._def
|
||||
let instance = eventInput._instance
|
||||
let currentData = this.getCurrentData()
|
||||
|
||||
// not already present? don't want to add an old snapshot
|
||||
if (!currentData.eventStore.defs[def.defId]) {
|
||||
this.dispatch({
|
||||
type: 'ADD_EVENTS',
|
||||
eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
|
||||
})
|
||||
this.triggerEventAdd(eventInput)
|
||||
}
|
||||
|
||||
return eventInput
|
||||
}
|
||||
|
||||
let state = this.getCurrentData()
|
||||
let eventSource: EventSource<any>
|
||||
|
||||
if (sourceInput instanceof EventSourceImpl) {
|
||||
eventSource = sourceInput.internalEventSource
|
||||
} else if (typeof sourceInput === 'boolean') {
|
||||
if (sourceInput) { // true. part of the first event source
|
||||
[eventSource] = hashValuesToArray(state.eventSources)
|
||||
}
|
||||
} else if (sourceInput != null) { // an ID. accepts a number too
|
||||
let sourceApi = this.getEventSourceById(sourceInput) // TODO: use an internal function
|
||||
|
||||
if (!sourceApi) {
|
||||
console.warn(`Could not find an event source with ID "${sourceInput}"`) // TODO: test
|
||||
return null
|
||||
}
|
||||
eventSource = sourceApi.internalEventSource
|
||||
}
|
||||
|
||||
let tuple = parseEvent(eventInput, eventSource, state, false)
|
||||
|
||||
if (tuple) {
|
||||
let newEventApi = new EventImpl(
|
||||
state,
|
||||
tuple.def,
|
||||
tuple.def.recurringDef ? null : tuple.instance,
|
||||
)
|
||||
this.dispatch({
|
||||
type: 'ADD_EVENTS',
|
||||
eventStore: eventTupleToStore(tuple),
|
||||
})
|
||||
this.triggerEventAdd(newEventApi)
|
||||
|
||||
return newEventApi
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private triggerEventAdd(eventApi: EventImpl): void {
|
||||
let { emitter } = this.getCurrentData()
|
||||
|
||||
emitter.trigger('eventAdd', {
|
||||
event: eventApi,
|
||||
relatedEvents: [],
|
||||
revert: () => {
|
||||
this.dispatch({
|
||||
type: 'REMOVE_EVENTS',
|
||||
eventStore: eventApiToStore(eventApi),
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: optimize
|
||||
getEventById(id: string): EventImpl | null {
|
||||
let state = this.getCurrentData()
|
||||
let { defs, instances } = state.eventStore
|
||||
id = String(id)
|
||||
|
||||
for (let defId in defs) {
|
||||
let def = defs[defId]
|
||||
|
||||
if (def.publicId === id) {
|
||||
if (def.recurringDef) {
|
||||
return new EventImpl(state, def, null)
|
||||
}
|
||||
|
||||
for (let instanceId in instances) {
|
||||
let instance = instances[instanceId]
|
||||
|
||||
if (instance.defId === def.defId) {
|
||||
return new EventImpl(state, def, instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
getEvents(): EventImpl[] {
|
||||
let currentData = this.getCurrentData()
|
||||
|
||||
return buildEventApis(currentData.eventStore, currentData)
|
||||
}
|
||||
|
||||
removeAllEvents(): void {
|
||||
this.dispatch({ type: 'REMOVE_ALL_EVENTS' })
|
||||
}
|
||||
|
||||
// Public Event Sources API
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
getEventSources(): EventSourceImpl[] {
|
||||
let state = this.getCurrentData()
|
||||
let sourceHash = state.eventSources
|
||||
let sourceApis: EventSourceImpl[] = []
|
||||
|
||||
for (let internalId in sourceHash) {
|
||||
sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]))
|
||||
}
|
||||
|
||||
return sourceApis
|
||||
}
|
||||
|
||||
getEventSourceById(id: string): EventSourceImpl | null {
|
||||
let state = this.getCurrentData()
|
||||
let sourceHash = state.eventSources
|
||||
id = String(id)
|
||||
|
||||
for (let sourceId in sourceHash) {
|
||||
if (sourceHash[sourceId].publicId === id) {
|
||||
return new EventSourceImpl(state, sourceHash[sourceId])
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
addEventSource(sourceInput: EventSourceInput): EventSourceImpl {
|
||||
let state = this.getCurrentData()
|
||||
|
||||
if (sourceInput instanceof EventSourceImpl) {
|
||||
// not already present? don't want to add an old snapshot
|
||||
if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
|
||||
this.dispatch({
|
||||
type: 'ADD_EVENT_SOURCES',
|
||||
sources: [sourceInput.internalEventSource],
|
||||
})
|
||||
}
|
||||
|
||||
return sourceInput
|
||||
}
|
||||
|
||||
let eventSource = parseEventSource(sourceInput, state)
|
||||
|
||||
if (eventSource) { // TODO: error otherwise?
|
||||
this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] })
|
||||
|
||||
return new EventSourceImpl(state, eventSource)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
removeAllEventSources(): void {
|
||||
this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' })
|
||||
}
|
||||
|
||||
refetchEvents(): void {
|
||||
this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true })
|
||||
}
|
||||
|
||||
// Scroll
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
scrollToTime(timeInput: DurationInput): void {
|
||||
let time = createDuration(timeInput)
|
||||
|
||||
if (time) {
|
||||
this.trigger('_scrollRequest', { time })
|
||||
}
|
||||
}
|
||||
}
|
45
fullcalendar-main/packages/core/src/api/EventApi.ts
Normal file
45
fullcalendar-main/packages/core/src/api/EventApi.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { Dictionary } from '../options.js'
|
||||
import { EventSourceApi } from './EventSourceApi.js'
|
||||
import {
|
||||
DateInput,
|
||||
DurationInput,
|
||||
FormatterInput,
|
||||
} from './structs.js'
|
||||
|
||||
export interface EventApi {
|
||||
source: EventSourceApi | null
|
||||
start: Date | null
|
||||
end: Date | null
|
||||
startStr: string
|
||||
endStr: string
|
||||
id: string
|
||||
groupId: string
|
||||
allDay: boolean
|
||||
title: string
|
||||
url: string
|
||||
display: string // TODO: better
|
||||
startEditable: boolean
|
||||
durationEditable: boolean
|
||||
constraint: any // TODO: better
|
||||
overlap: boolean
|
||||
allow: any // TODO: better
|
||||
backgroundColor: string
|
||||
borderColor: string
|
||||
textColor: string
|
||||
classNames: string[]
|
||||
extendedProps: Dictionary
|
||||
|
||||
setProp(name: string, val: any): void
|
||||
setExtendedProp(name: string, val: any): void
|
||||
setStart(startInput: DateInput, options?: { granularity?: string, maintainDuration?: boolean }): void
|
||||
setEnd(endInput: DateInput | null, options?: { granularity?: string }): void
|
||||
setDates(startInput: DateInput, endInput: DateInput | null, options?: { allDay?: boolean, granularity?: string }): void
|
||||
moveStart(deltaInput: DurationInput): void
|
||||
moveEnd(deltaInput: DurationInput): void
|
||||
moveDates(deltaInput: DurationInput): void
|
||||
setAllDay(allDay: boolean, options?: { maintainDuration?: boolean }): void
|
||||
formatRange(formatInput: FormatterInput)
|
||||
remove(): void
|
||||
toPlainObject(settings?: { collapseExtendedProps?: boolean, collapseColor?: boolean }): Dictionary
|
||||
toJSON(): Dictionary
|
||||
}
|
451
fullcalendar-main/packages/core/src/api/EventImpl.ts
Normal file
451
fullcalendar-main/packages/core/src/api/EventImpl.ts
Normal file
|
@ -0,0 +1,451 @@
|
|||
import { EventDef } from '../structs/event-def.js'
|
||||
import { EVENT_NON_DATE_REFINERS, EVENT_DATE_REFINERS } from '../structs/event-parse.js'
|
||||
import { EventInstance } from '../structs/event-instance.js'
|
||||
import { EVENT_UI_REFINERS, EventUiHash } from '../component/event-ui.js'
|
||||
import { EventMutation, applyMutationToEventStore } from '../structs/event-mutation.js'
|
||||
import { diffDates, computeAlignedDayRange } from '../util/date.js'
|
||||
import { createDuration, durationsEqual } from '../datelib/duration.js'
|
||||
import { createFormatter } from '../datelib/formatting.js'
|
||||
import { CalendarContext } from '../CalendarContext.js'
|
||||
import { getRelevantEvents, EventStore } from '../structs/event-store.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
import { EventApi } from './EventApi.js'
|
||||
import { EventSourceImpl } from './EventSourceImpl.js'
|
||||
import {
|
||||
DateInput,
|
||||
DurationInput,
|
||||
FormatterInput,
|
||||
} from './structs.js'
|
||||
|
||||
export class EventImpl implements EventApi {
|
||||
_context: CalendarContext
|
||||
_def: EventDef
|
||||
_instance: EventInstance | null
|
||||
// instance will be null if expressing a recurring event that has no current instances,
|
||||
// OR if trying to validate an incoming external event that has no dates assigned
|
||||
|
||||
constructor(context: CalendarContext, def: EventDef, instance?: EventInstance) {
|
||||
this._context = context
|
||||
this._def = def
|
||||
this._instance = instance || null
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: make event struct more responsible for this
|
||||
*/
|
||||
setProp(name: string, val: any): void {
|
||||
if (name in EVENT_DATE_REFINERS) {
|
||||
console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.')
|
||||
// TODO: make proper aliasing system?
|
||||
} else if (name === 'id') {
|
||||
val = EVENT_NON_DATE_REFINERS[name](val)
|
||||
|
||||
this.mutate({
|
||||
standardProps: { publicId: val }, // hardcoded internal name
|
||||
})
|
||||
} else if (name in EVENT_NON_DATE_REFINERS) {
|
||||
val = EVENT_NON_DATE_REFINERS[name](val)
|
||||
|
||||
this.mutate({
|
||||
standardProps: { [name]: val },
|
||||
})
|
||||
} else if (name in EVENT_UI_REFINERS) {
|
||||
let ui = EVENT_UI_REFINERS[name](val)
|
||||
|
||||
if (name === 'color') {
|
||||
ui = { backgroundColor: val, borderColor: val }
|
||||
} else if (name === 'editable') {
|
||||
ui = { startEditable: val, durationEditable: val }
|
||||
} else {
|
||||
ui = { [name]: val }
|
||||
}
|
||||
|
||||
this.mutate({
|
||||
standardProps: { ui },
|
||||
})
|
||||
} else {
|
||||
console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`)
|
||||
}
|
||||
}
|
||||
|
||||
setExtendedProp(name: string, val: any): void {
|
||||
this.mutate({
|
||||
extendedProps: { [name]: val },
|
||||
})
|
||||
}
|
||||
|
||||
setStart(startInput: DateInput, options: { granularity?: string, maintainDuration?: boolean } = {}): void {
|
||||
let { dateEnv } = this._context
|
||||
let start = dateEnv.createMarker(startInput)
|
||||
|
||||
if (start && this._instance) { // TODO: warning if parsed bad
|
||||
let instanceRange = this._instance.range
|
||||
let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity) // what if parsed bad!?
|
||||
|
||||
if (options.maintainDuration) {
|
||||
this.mutate({ datesDelta: startDelta })
|
||||
} else {
|
||||
this.mutate({ startDelta })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setEnd(endInput: DateInput | null, options: { granularity?: string } = {}): void {
|
||||
let { dateEnv } = this._context
|
||||
let end
|
||||
|
||||
if (endInput != null) {
|
||||
end = dateEnv.createMarker(endInput)
|
||||
|
||||
if (!end) {
|
||||
return // TODO: warning if parsed bad
|
||||
}
|
||||
}
|
||||
|
||||
if (this._instance) {
|
||||
if (end) {
|
||||
let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity)
|
||||
this.mutate({ endDelta })
|
||||
} else {
|
||||
this.mutate({ standardProps: { hasEnd: false } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDates(startInput: DateInput, endInput: DateInput | null, options: { allDay?: boolean, granularity?: string } = {}): void {
|
||||
let { dateEnv } = this._context
|
||||
let standardProps = { allDay: options.allDay } as any
|
||||
let start = dateEnv.createMarker(startInput)
|
||||
let end
|
||||
|
||||
if (!start) {
|
||||
return // TODO: warning if parsed bad
|
||||
}
|
||||
|
||||
if (endInput != null) {
|
||||
end = dateEnv.createMarker(endInput)
|
||||
|
||||
if (!end) { // TODO: warning if parsed bad
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (this._instance) {
|
||||
let instanceRange = this._instance.range
|
||||
|
||||
// when computing the diff for an event being converted to all-day,
|
||||
// compute diff off of the all-day values the way event-mutation does.
|
||||
if (options.allDay === true) {
|
||||
instanceRange = computeAlignedDayRange(instanceRange)
|
||||
}
|
||||
|
||||
let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity)
|
||||
|
||||
if (end) {
|
||||
let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity)
|
||||
|
||||
if (durationsEqual(startDelta, endDelta)) {
|
||||
this.mutate({ datesDelta: startDelta, standardProps })
|
||||
} else {
|
||||
this.mutate({ startDelta, endDelta, standardProps })
|
||||
}
|
||||
} else { // means "clear the end"
|
||||
standardProps.hasEnd = false
|
||||
this.mutate({ datesDelta: startDelta, standardProps })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveStart(deltaInput: DurationInput): void {
|
||||
let delta = createDuration(deltaInput)
|
||||
|
||||
if (delta) { // TODO: warning if parsed bad
|
||||
this.mutate({ startDelta: delta })
|
||||
}
|
||||
}
|
||||
|
||||
moveEnd(deltaInput: DurationInput): void {
|
||||
let delta = createDuration(deltaInput)
|
||||
|
||||
if (delta) { // TODO: warning if parsed bad
|
||||
this.mutate({ endDelta: delta })
|
||||
}
|
||||
}
|
||||
|
||||
moveDates(deltaInput: DurationInput): void {
|
||||
let delta = createDuration(deltaInput)
|
||||
|
||||
if (delta) { // TODO: warning if parsed bad
|
||||
this.mutate({ datesDelta: delta })
|
||||
}
|
||||
}
|
||||
|
||||
setAllDay(allDay: boolean, options: { maintainDuration?: boolean } = {}): void {
|
||||
let standardProps = { allDay } as any
|
||||
let { maintainDuration } = options
|
||||
|
||||
if (maintainDuration == null) {
|
||||
maintainDuration = this._context.options.allDayMaintainDuration
|
||||
}
|
||||
|
||||
if (this._def.allDay !== allDay) {
|
||||
standardProps.hasEnd = maintainDuration
|
||||
}
|
||||
|
||||
this.mutate({ standardProps })
|
||||
}
|
||||
|
||||
formatRange(formatInput: FormatterInput): string {
|
||||
let { dateEnv } = this._context
|
||||
let instance = this._instance
|
||||
let formatter = createFormatter(formatInput)
|
||||
|
||||
if (this._def.hasEnd) {
|
||||
return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {
|
||||
forcedStartTzo: instance.forcedStartTzo,
|
||||
forcedEndTzo: instance.forcedEndTzo,
|
||||
})
|
||||
}
|
||||
return dateEnv.format(instance.range.start, formatter, {
|
||||
forcedTzo: instance.forcedStartTzo,
|
||||
})
|
||||
}
|
||||
|
||||
mutate(mutation: EventMutation): void { // meant to be private. but plugins need access
|
||||
let instance = this._instance
|
||||
|
||||
if (instance) {
|
||||
let def = this._def
|
||||
let context = this._context
|
||||
let { eventStore } = context.getCurrentData()
|
||||
let relevantEvents = getRelevantEvents(eventStore, instance.instanceId)
|
||||
let eventConfigBase = {
|
||||
'': { // HACK. always allow API to mutate events
|
||||
display: '',
|
||||
startEditable: true,
|
||||
durationEditable: true,
|
||||
constraints: [],
|
||||
overlap: null,
|
||||
allows: [],
|
||||
backgroundColor: '',
|
||||
borderColor: '',
|
||||
textColor: '',
|
||||
classNames: [],
|
||||
},
|
||||
} as EventUiHash
|
||||
|
||||
relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context)
|
||||
|
||||
let oldEvent = new EventImpl(context, def, instance) // snapshot
|
||||
this._def = relevantEvents.defs[def.defId]
|
||||
this._instance = relevantEvents.instances[instance.instanceId]
|
||||
|
||||
context.dispatch({
|
||||
type: 'MERGE_EVENTS',
|
||||
eventStore: relevantEvents,
|
||||
})
|
||||
|
||||
context.emitter.trigger('eventChange', {
|
||||
oldEvent,
|
||||
event: this,
|
||||
relatedEvents: buildEventApis(relevantEvents, context, instance),
|
||||
revert() {
|
||||
context.dispatch({
|
||||
type: 'RESET_EVENTS',
|
||||
eventStore, // the ORIGINAL store
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
remove(): void {
|
||||
let context = this._context
|
||||
let asStore = eventApiToStore(this)
|
||||
|
||||
context.dispatch({
|
||||
type: 'REMOVE_EVENTS',
|
||||
eventStore: asStore,
|
||||
})
|
||||
|
||||
context.emitter.trigger('eventRemove', {
|
||||
event: this,
|
||||
relatedEvents: [],
|
||||
revert() {
|
||||
context.dispatch({
|
||||
type: 'MERGE_EVENTS',
|
||||
eventStore: asStore,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
get source(): EventSourceImpl | null {
|
||||
let { sourceId } = this._def
|
||||
|
||||
if (sourceId) {
|
||||
return new EventSourceImpl(
|
||||
this._context,
|
||||
this._context.getCurrentData().eventSources[sourceId],
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
get start(): Date | null {
|
||||
return this._instance ?
|
||||
this._context.dateEnv.toDate(this._instance.range.start) :
|
||||
null
|
||||
}
|
||||
|
||||
get end(): Date | null {
|
||||
return (this._instance && this._def.hasEnd) ?
|
||||
this._context.dateEnv.toDate(this._instance.range.end) :
|
||||
null
|
||||
}
|
||||
|
||||
get startStr(): string {
|
||||
let instance = this._instance
|
||||
if (instance) {
|
||||
return this._context.dateEnv.formatIso(instance.range.start, {
|
||||
omitTime: this._def.allDay,
|
||||
forcedTzo: instance.forcedStartTzo,
|
||||
})
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
get endStr(): string {
|
||||
let instance = this._instance
|
||||
if (instance && this._def.hasEnd) {
|
||||
return this._context.dateEnv.formatIso(instance.range.end, {
|
||||
omitTime: this._def.allDay,
|
||||
forcedTzo: instance.forcedEndTzo,
|
||||
})
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// computable props that all access the def
|
||||
// TODO: find a TypeScript-compatible way to do this at scale
|
||||
get id() { return this._def.publicId }
|
||||
get groupId() { return this._def.groupId }
|
||||
get allDay() { return this._def.allDay }
|
||||
get title() { return this._def.title }
|
||||
get url() { return this._def.url }
|
||||
get display() { return this._def.ui.display || 'auto' } // bad. just normalize the type earlier
|
||||
get startEditable() { return this._def.ui.startEditable }
|
||||
get durationEditable() { return this._def.ui.durationEditable }
|
||||
get constraint() { return this._def.ui.constraints[0] || null }
|
||||
get overlap() { return this._def.ui.overlap }
|
||||
get allow() { return this._def.ui.allows[0] || null }
|
||||
get backgroundColor() { return this._def.ui.backgroundColor }
|
||||
get borderColor() { return this._def.ui.borderColor }
|
||||
get textColor() { return this._def.ui.textColor }
|
||||
|
||||
// NOTE: user can't modify these because Object.freeze was called in event-def parsing
|
||||
get classNames() { return this._def.ui.classNames }
|
||||
get extendedProps() { return this._def.extendedProps }
|
||||
|
||||
toPlainObject(settings: { collapseExtendedProps?: boolean, collapseColor?: boolean } = {}): Dictionary {
|
||||
let def = this._def
|
||||
let { ui } = def
|
||||
let { startStr, endStr } = this
|
||||
let res: Dictionary = {
|
||||
allDay: def.allDay,
|
||||
}
|
||||
|
||||
if (def.title) {
|
||||
res.title = def.title
|
||||
}
|
||||
|
||||
if (startStr) {
|
||||
res.start = startStr
|
||||
}
|
||||
|
||||
if (endStr) {
|
||||
res.end = endStr
|
||||
}
|
||||
|
||||
if (def.publicId) {
|
||||
res.id = def.publicId
|
||||
}
|
||||
|
||||
if (def.groupId) {
|
||||
res.groupId = def.groupId
|
||||
}
|
||||
|
||||
if (def.url) {
|
||||
res.url = def.url
|
||||
}
|
||||
|
||||
if (ui.display && ui.display !== 'auto') {
|
||||
res.display = ui.display
|
||||
}
|
||||
|
||||
// TODO: what about recurring-event properties???
|
||||
// TODO: include startEditable/durationEditable/constraint/overlap/allow
|
||||
|
||||
if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) {
|
||||
res.color = ui.backgroundColor
|
||||
} else {
|
||||
if (ui.backgroundColor) {
|
||||
res.backgroundColor = ui.backgroundColor
|
||||
}
|
||||
if (ui.borderColor) {
|
||||
res.borderColor = ui.borderColor
|
||||
}
|
||||
}
|
||||
|
||||
if (ui.textColor) {
|
||||
res.textColor = ui.textColor
|
||||
}
|
||||
|
||||
if (ui.classNames.length) {
|
||||
res.classNames = ui.classNames
|
||||
}
|
||||
|
||||
if (Object.keys(def.extendedProps).length) {
|
||||
if (settings.collapseExtendedProps) {
|
||||
Object.assign(res, def.extendedProps)
|
||||
} else {
|
||||
res.extendedProps = def.extendedProps
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
toJSON(): Dictionary {
|
||||
return this.toPlainObject()
|
||||
}
|
||||
}
|
||||
|
||||
export function eventApiToStore(eventApi: EventImpl): EventStore {
|
||||
let def = eventApi._def
|
||||
let instance = eventApi._instance
|
||||
|
||||
return {
|
||||
defs: { [def.defId]: def },
|
||||
instances: instance
|
||||
? { [instance.instanceId]: instance }
|
||||
: {},
|
||||
}
|
||||
}
|
||||
|
||||
export function buildEventApis(eventStore: EventStore, context: CalendarContext, excludeInstance?: EventInstance): EventImpl[] {
|
||||
let { defs, instances } = eventStore
|
||||
let eventApis: EventImpl[] = []
|
||||
let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''
|
||||
|
||||
for (let id in instances) {
|
||||
let instance = instances[id]
|
||||
let def = defs[instance.defId]
|
||||
|
||||
if (instance.instanceId !== excludeInstanceId) {
|
||||
eventApis.push(new EventImpl(context, def, instance))
|
||||
}
|
||||
}
|
||||
|
||||
return eventApis
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
export interface EventSourceApi {
|
||||
id: string
|
||||
url: string
|
||||
format: string
|
||||
remove(): void
|
||||
refetch(): void
|
||||
}
|
38
fullcalendar-main/packages/core/src/api/EventSourceImpl.ts
Normal file
38
fullcalendar-main/packages/core/src/api/EventSourceImpl.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { EventSource } from '../structs/event-source.js'
|
||||
import { CalendarContext } from '../CalendarContext.js'
|
||||
import { EventSourceApi } from './EventSourceApi.js'
|
||||
|
||||
export class EventSourceImpl implements EventSourceApi {
|
||||
constructor(
|
||||
private context: CalendarContext,
|
||||
public internalEventSource: EventSource<any>, // rename?
|
||||
) {
|
||||
}
|
||||
|
||||
remove(): void {
|
||||
this.context.dispatch({
|
||||
type: 'REMOVE_EVENT_SOURCE',
|
||||
sourceId: this.internalEventSource.sourceId,
|
||||
})
|
||||
}
|
||||
|
||||
refetch(): void {
|
||||
this.context.dispatch({
|
||||
type: 'FETCH_EVENT_SOURCES',
|
||||
sourceIds: [this.internalEventSource.sourceId],
|
||||
isRefetch: true,
|
||||
})
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this.internalEventSource.publicId
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return this.internalEventSource.meta.url
|
||||
}
|
||||
|
||||
get format(): string {
|
||||
return this.internalEventSource.meta.format // TODO: bad. not guaranteed
|
||||
}
|
||||
}
|
14
fullcalendar-main/packages/core/src/api/ViewApi.ts
Normal file
14
fullcalendar-main/packages/core/src/api/ViewApi.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { CalendarApi } from './CalendarApi.js'
|
||||
|
||||
export interface ViewApi {
|
||||
calendar: CalendarApi
|
||||
|
||||
type: string
|
||||
title: string
|
||||
activeStart: Date
|
||||
activeEnd: Date
|
||||
currentStart: Date
|
||||
currentEnd: Date
|
||||
|
||||
getOption(name: string): any
|
||||
}
|
42
fullcalendar-main/packages/core/src/api/ViewImpl.ts
Normal file
42
fullcalendar-main/packages/core/src/api/ViewImpl.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { DateEnv } from '../datelib/env.js'
|
||||
import { CalendarData } from '../reducers/data-types.js'
|
||||
import { CalendarApi } from './CalendarApi.js'
|
||||
import { ViewApi } from './ViewApi.js'
|
||||
|
||||
// always represents the current view. otherwise, it'd need to change value every time date changes
|
||||
export class ViewImpl implements ViewApi {
|
||||
constructor(
|
||||
public type: string,
|
||||
private getCurrentData: () => CalendarData,
|
||||
private dateEnv: DateEnv,
|
||||
) {
|
||||
}
|
||||
|
||||
get calendar(): CalendarApi {
|
||||
return this.getCurrentData().calendarApi
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.getCurrentData().viewTitle
|
||||
}
|
||||
|
||||
get activeStart(): Date {
|
||||
return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start)
|
||||
}
|
||||
|
||||
get activeEnd(): Date {
|
||||
return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end)
|
||||
}
|
||||
|
||||
get currentStart(): Date {
|
||||
return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start)
|
||||
}
|
||||
|
||||
get currentEnd(): Date {
|
||||
return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end)
|
||||
}
|
||||
|
||||
getOption(name: string): any {
|
||||
return this.getCurrentData().options[name] // are the view-specific options
|
||||
}
|
||||
}
|
44
fullcalendar-main/packages/core/src/api/structs.ts
Normal file
44
fullcalendar-main/packages/core/src/api/structs.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export type { CalendarOptions, CalendarListeners } from '../options.js'
|
||||
export type { DateInput } from '../datelib/env.js'
|
||||
export type { DurationInput } from '../datelib/duration.js'
|
||||
export type { DateSpanInput } from '../structs/date-span.js'
|
||||
export type { DateRangeInput } from '../datelib/date-range.js'
|
||||
export type { EventSourceInput } from '../structs/event-source-parse.js'
|
||||
export type { EventSourceFunc, EventSourceFuncArg } from '../event-sources/func-event-source.js'
|
||||
export type { EventInput, EventInputTransformer } from '../structs/event-parse.js'
|
||||
export type { FormatterInput } from '../datelib/formatting.js'
|
||||
export type { CssDimValue } from '../scrollgrid/util.js'
|
||||
export type { BusinessHoursInput } from '../structs/business-hours.js'
|
||||
export type { LocaleSingularArg, LocaleInput } from '../datelib/locale.js'
|
||||
export type { OverlapFunc, ConstraintInput, AllowFunc } from '../structs/constraint.js'
|
||||
export type { PluginDef, PluginDefInput } from '../plugin-system-struct.js'
|
||||
export type { ViewComponentType, SpecificViewContentArg, SpecificViewMountArg } from '../structs/view-config.js'
|
||||
export type { ClassNamesGenerator, CustomContentGenerator, DidMountHandler, WillUnmountHandler } from '../common/render-hook.js'
|
||||
export type { NowIndicatorContentArg, NowIndicatorMountArg } from '../common/NowIndicatorContainer.js'
|
||||
export type { WeekNumberContentArg, WeekNumberMountArg } from '../common/WeekNumberContainer.js'
|
||||
export type { MoreLinkContentArg, MoreLinkMountArg } from '../common/MoreLinkContainer.js'
|
||||
export * from '../common/more-link-public-types.js'
|
||||
export type {
|
||||
SlotLaneContentArg, SlotLaneMountArg,
|
||||
SlotLabelContentArg, SlotLabelMountArg,
|
||||
AllDayContentArg, AllDayMountArg,
|
||||
DayHeaderContentArg,
|
||||
DayHeaderMountArg,
|
||||
} from '../render-hook-misc.js'
|
||||
export type { DayCellContentArg, DayCellMountArg } from '../common/DayCellContainer.js'
|
||||
export type { ViewContentArg, ViewMountArg } from '../common/ViewContainer.js'
|
||||
export type { EventClickArg } from '../interactions/EventClicking.js'
|
||||
export type { EventHoveringArg } from '../interactions/EventHovering.js'
|
||||
export type { DateSelectArg, DateUnselectArg } from '../calendar-utils.js'
|
||||
export type { WeekNumberCalculation } from '../datelib/env.js'
|
||||
export type { ToolbarInput, CustomButtonInput, ButtonIconsInput, ButtonTextCompoundInput } from '../toolbar-struct.js'
|
||||
export type { EventContentArg, EventMountArg } from '../component/event-rendering.js'
|
||||
export type { DatesSetArg } from '../dates-set.js'
|
||||
export type { EventAddArg, EventChangeArg, EventDropArg, EventRemoveArg } from '../event-crud.js'
|
||||
export type { ButtonHintCompoundInput } from '../toolbar-struct.js'
|
||||
export type { CustomRenderingHandler, CustomRenderingStore } from '../content-inject/CustomRenderingStore.js'
|
||||
export type { DateSpanApi, DatePointApi } from '../structs/date-span.js'
|
||||
export type { DateSelectionApi } from '../calendar-utils.js'
|
||||
|
||||
// used by some args
|
||||
export type { Duration } from '../datelib/duration.js'
|
79
fullcalendar-main/packages/core/src/calendar-utils.ts
Normal file
79
fullcalendar-main/packages/core/src/calendar-utils.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { PointerDragEvent } from './interactions/pointer.js'
|
||||
import { buildDateSpanApi, DateSpanApi, DatePointApi, DateSpan } from './structs/date-span.js'
|
||||
import { CalendarContext } from './CalendarContext.js'
|
||||
import { ViewApi } from './api/ViewApi.js'
|
||||
import { ViewImpl } from './api/ViewImpl.js'
|
||||
import { DateMarker, startOfDay } from './datelib/marker.js'
|
||||
|
||||
export interface DateClickApi extends DatePointApi {
|
||||
dayEl: HTMLElement
|
||||
jsEvent: UIEvent
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export interface DateSelectionApi extends DateSpanApi {
|
||||
jsEvent: UIEvent
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export type DatePointTransform = (dateSpan: DateSpan, context: CalendarContext) => any
|
||||
export type DateSpanTransform = (dateSpan: DateSpan, context: CalendarContext) => any
|
||||
|
||||
export type CalendarInteraction = { destroy: () => void }
|
||||
export type CalendarInteractionClass = { new(context: CalendarContext): CalendarInteraction }
|
||||
|
||||
export type OptionChangeHandler = (propValue: any, context: CalendarContext) => void
|
||||
export type OptionChangeHandlerMap = { [propName: string]: OptionChangeHandler }
|
||||
|
||||
export interface DateSelectArg extends DateSpanApi {
|
||||
jsEvent: MouseEvent | null
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export function triggerDateSelect(selection: DateSpan, pev: PointerDragEvent | null, context: CalendarContext & { viewApi?: ViewImpl }) {
|
||||
context.emitter.trigger('select', {
|
||||
...buildDateSpanApiWithContext(selection, context),
|
||||
jsEvent: pev ? pev.origEvent as MouseEvent : null, // Is this always a mouse event? See #4655
|
||||
view: context.viewApi || context.calendarApi.view,
|
||||
} as DateSelectArg)
|
||||
}
|
||||
|
||||
export interface DateUnselectArg {
|
||||
jsEvent: MouseEvent
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export function triggerDateUnselect(pev: PointerDragEvent | null, context: CalendarContext & { viewApi?: ViewImpl }) {
|
||||
context.emitter.trigger('unselect', {
|
||||
jsEvent: pev ? pev.origEvent as MouseEvent : null, // Is this always a mouse event? See #4655
|
||||
view: context.viewApi || context.calendarApi.view,
|
||||
} as DateUnselectArg)
|
||||
}
|
||||
|
||||
export function buildDateSpanApiWithContext(dateSpan: DateSpan, context: CalendarContext) {
|
||||
let props = {} as DateSpanApi
|
||||
|
||||
for (let transform of context.pluginHooks.dateSpanTransforms) {
|
||||
Object.assign(props, transform(dateSpan, context))
|
||||
}
|
||||
|
||||
Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv))
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
// Given an event's allDay status and start date, return what its fallback end date should be.
|
||||
// TODO: rename to computeDefaultEventEnd
|
||||
export function getDefaultEventEnd(allDay: boolean, marker: DateMarker, context: CalendarContext): DateMarker {
|
||||
let { dateEnv, options } = context
|
||||
let end = marker
|
||||
|
||||
if (allDay) {
|
||||
end = startOfDay(end)
|
||||
end = dateEnv.add(end, options.defaultAllDayEventDuration)
|
||||
} else {
|
||||
end = dateEnv.add(end, options.defaultTimedEventDuration)
|
||||
}
|
||||
|
||||
return end
|
||||
}
|
117
fullcalendar-main/packages/core/src/common/DayCellContainer.tsx
Normal file
117
fullcalendar-main/packages/core/src/common/DayCellContainer.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { ComponentChild, createElement } from '../preact.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { getDateMeta, DateMeta, getDayClassNames } from '../component/date-rendering.js'
|
||||
import { createFormatter } from '../datelib/formatting.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
import { formatDayString } from '../datelib/formatting-utils.js'
|
||||
import { MountArg } from './render-hook.js'
|
||||
import { ViewApi } from '../api/ViewApi.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { DateProfile } from '../DateProfileGenerator.js'
|
||||
import { memoizeObjArg } from '../util/memoize.js'
|
||||
import { Dictionary, ViewOptions } from '../options.js'
|
||||
import { DateEnv } from '../datelib/env.js'
|
||||
import { ContentContainer, InnerContainerFunc } from '../content-inject/ContentContainer.js'
|
||||
import { ElProps, hasCustomRenderingHandler } from '../content-inject/ContentInjector.js'
|
||||
|
||||
export interface DayCellContentArg extends DateMeta {
|
||||
date: DateMarker // localized
|
||||
view: ViewApi
|
||||
dayNumberText: string
|
||||
[extraProp: string]: any // so can include a resource
|
||||
}
|
||||
|
||||
export type DayCellMountArg = MountArg<DayCellContentArg>
|
||||
|
||||
export interface DayCellContainerProps extends Partial<ElProps> {
|
||||
date: DateMarker
|
||||
dateProfile: DateProfile
|
||||
todayRange: DateRange
|
||||
isMonthStart?: boolean
|
||||
showDayNumber?: boolean // defaults to false
|
||||
extraRenderProps?: Dictionary
|
||||
defaultGenerator?: (renderProps: DayCellContentArg) => ComponentChild
|
||||
children?: InnerContainerFunc<DayCellContentArg>
|
||||
}
|
||||
|
||||
const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' })
|
||||
|
||||
export class DayCellContainer extends BaseComponent<DayCellContainerProps> {
|
||||
refineRenderProps = memoizeObjArg(refineRenderProps)
|
||||
|
||||
render() {
|
||||
let { props, context } = this
|
||||
let { options } = context
|
||||
let renderProps = this.refineRenderProps({
|
||||
date: props.date,
|
||||
dateProfile: props.dateProfile,
|
||||
todayRange: props.todayRange,
|
||||
isMonthStart: props.isMonthStart || false,
|
||||
showDayNumber: props.showDayNumber,
|
||||
extraRenderProps: props.extraRenderProps,
|
||||
viewApi: context.viewApi,
|
||||
dateEnv: context.dateEnv,
|
||||
monthStartFormat: options.monthStartFormat,
|
||||
})
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
{...props /* includes children */}
|
||||
elClasses={[
|
||||
...getDayClassNames(renderProps, context.theme),
|
||||
...(props.elClasses || []),
|
||||
]}
|
||||
elAttrs={{
|
||||
...props.elAttrs,
|
||||
...(renderProps.isDisabled ? {} : { 'data-date': formatDayString(props.date) }),
|
||||
}}
|
||||
renderProps={renderProps}
|
||||
generatorName="dayCellContent"
|
||||
customGenerator={options.dayCellContent}
|
||||
defaultGenerator={props.defaultGenerator}
|
||||
classNameGenerator={
|
||||
// don't use custom classNames if disabled
|
||||
renderProps.isDisabled ? undefined : options.dayCellClassNames
|
||||
}
|
||||
didMount={options.dayCellDidMount}
|
||||
willUnmount={options.dayCellWillUnmount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function hasCustomDayCellContent(options: ViewOptions): boolean {
|
||||
return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options))
|
||||
}
|
||||
|
||||
// Render Props
|
||||
|
||||
interface DayCellRenderPropsInput {
|
||||
date: DateMarker // generic
|
||||
dateProfile: DateProfile
|
||||
todayRange: DateRange
|
||||
dateEnv: DateEnv
|
||||
viewApi: ViewApi
|
||||
monthStartFormat: DateFormatter
|
||||
isMonthStart: boolean // defaults to false
|
||||
showDayNumber?: boolean // defaults to false
|
||||
extraRenderProps?: Dictionary // so can include a resource
|
||||
}
|
||||
|
||||
function refineRenderProps(raw: DayCellRenderPropsInput): DayCellContentArg {
|
||||
let { date, dateEnv, dateProfile, isMonthStart } = raw
|
||||
let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile)
|
||||
let dayNumberText = raw.showDayNumber ? (
|
||||
dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)
|
||||
) : ''
|
||||
|
||||
return {
|
||||
date: dateEnv.toDate(date),
|
||||
view: raw.viewApi,
|
||||
...dayMeta,
|
||||
isMonthStart,
|
||||
dayNumberText,
|
||||
...raw.extraRenderProps,
|
||||
}
|
||||
}
|
65
fullcalendar-main/packages/core/src/common/DayHeader.tsx
Normal file
65
fullcalendar-main/packages/core/src/common/DayHeader.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { computeFallbackHeaderFormat } from './table-utils.js'
|
||||
import { VNode, createElement } from '../preact.js'
|
||||
import { TableDateCell } from './TableDateCell.js'
|
||||
import { TableDowCell } from './TableDowCell.js'
|
||||
import { NowTimer } from '../NowTimer.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { memoize } from '../util/memoize.js'
|
||||
import { DateProfile } from '../DateProfileGenerator.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
|
||||
export interface DayHeaderProps {
|
||||
dateProfile: DateProfile
|
||||
dates: DateMarker[]
|
||||
datesRepDistinctDays: boolean
|
||||
renderIntro?: (rowKey: string) => VNode
|
||||
}
|
||||
|
||||
export class DayHeader extends BaseComponent<DayHeaderProps> { // TODO: rename to DayHeaderTr?
|
||||
createDayHeaderFormatter = memoize(createDayHeaderFormatter)
|
||||
|
||||
render() {
|
||||
let { context } = this
|
||||
let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props
|
||||
|
||||
let dayHeaderFormat = this.createDayHeaderFormatter(
|
||||
context.options.dayHeaderFormat,
|
||||
datesRepDistinctDays,
|
||||
dates.length,
|
||||
)
|
||||
|
||||
return (
|
||||
<NowTimer unit="day">
|
||||
{(nowDate: DateMarker, todayRange: DateRange) => (
|
||||
<tr role="row">
|
||||
{renderIntro && renderIntro('day')}
|
||||
{dates.map((date) => (
|
||||
datesRepDistinctDays ? (
|
||||
<TableDateCell
|
||||
key={date.toISOString()}
|
||||
date={date}
|
||||
dateProfile={dateProfile}
|
||||
todayRange={todayRange}
|
||||
colCnt={dates.length}
|
||||
dayHeaderFormat={dayHeaderFormat}
|
||||
/>
|
||||
) : (
|
||||
<TableDowCell
|
||||
key={date.getUTCDay()}
|
||||
dow={date.getUTCDay()}
|
||||
dayHeaderFormat={dayHeaderFormat}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
</tr>
|
||||
)}
|
||||
</NowTimer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function createDayHeaderFormatter(explicitFormat: DateFormatter, datesRepDistinctDays, dateCnt) {
|
||||
return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt)
|
||||
}
|
81
fullcalendar-main/packages/core/src/common/DaySeriesModel.ts
Normal file
81
fullcalendar-main/packages/core/src/common/DaySeriesModel.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { DateProfileGenerator } from '../DateProfileGenerator.js'
|
||||
import { DateMarker, addDays, diffDays } from '../datelib/marker.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
|
||||
export interface DaySeriesSeg {
|
||||
firstIndex: number
|
||||
lastIndex: number
|
||||
isStart: boolean
|
||||
isEnd: boolean
|
||||
}
|
||||
|
||||
export class DaySeriesModel {
|
||||
cnt: number
|
||||
dates: DateMarker[] // whole-day dates for each column. left to right
|
||||
indices: number[] // for each day from start, the offset
|
||||
|
||||
constructor(range: DateRange, dateProfileGenerator: DateProfileGenerator) {
|
||||
let date: DateMarker = range.start
|
||||
let { end } = range
|
||||
let indices: number[] = []
|
||||
let dates: DateMarker[] = []
|
||||
let dayIndex = -1
|
||||
|
||||
while (date < end) { // loop each day from start to end
|
||||
if (dateProfileGenerator.isHiddenDay(date)) {
|
||||
indices.push(dayIndex + 0.5) // mark that it's between indices
|
||||
} else {
|
||||
dayIndex += 1
|
||||
indices.push(dayIndex)
|
||||
dates.push(date)
|
||||
}
|
||||
date = addDays(date, 1)
|
||||
}
|
||||
|
||||
this.dates = dates
|
||||
this.indices = indices
|
||||
this.cnt = dates.length
|
||||
}
|
||||
|
||||
sliceRange(range: DateRange): DaySeriesSeg | null {
|
||||
let firstIndex = this.getDateDayIndex(range.start) // inclusive first index
|
||||
let lastIndex = this.getDateDayIndex(addDays(range.end, -1)) // inclusive last index
|
||||
|
||||
let clippedFirstIndex = Math.max(0, firstIndex)
|
||||
let clippedLastIndex = Math.min(this.cnt - 1, lastIndex)
|
||||
|
||||
// deal with in-between indices
|
||||
clippedFirstIndex = Math.ceil(clippedFirstIndex) // in-between starts round to next cell
|
||||
clippedLastIndex = Math.floor(clippedLastIndex) // in-between ends round to prev cell
|
||||
|
||||
if (clippedFirstIndex <= clippedLastIndex) {
|
||||
return {
|
||||
firstIndex: clippedFirstIndex,
|
||||
lastIndex: clippedLastIndex,
|
||||
isStart: firstIndex === clippedFirstIndex,
|
||||
isEnd: lastIndex === clippedLastIndex,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Given a date, returns its chronolocial cell-index from the first cell of the grid.
|
||||
// If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
|
||||
// If before the first offset, returns a negative number.
|
||||
// If after the last offset, returns an offset past the last cell offset.
|
||||
// Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
|
||||
private getDateDayIndex(date: DateMarker) {
|
||||
let { indices } = this
|
||||
let dayOffset = Math.floor(diffDays(this.dates[0], date))
|
||||
|
||||
if (dayOffset < 0) {
|
||||
return indices[0] - 1
|
||||
}
|
||||
|
||||
if (dayOffset >= indices.length) {
|
||||
return indices[indices.length - 1] + 1
|
||||
}
|
||||
|
||||
return indices[dayOffset]
|
||||
}
|
||||
}
|
120
fullcalendar-main/packages/core/src/common/DayTableModel.ts
Normal file
120
fullcalendar-main/packages/core/src/common/DayTableModel.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { DaySeriesModel } from './DaySeriesModel.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { Seg } from '../component/DateComponent.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
|
||||
export interface DayTableSeg extends Seg {
|
||||
row: number
|
||||
firstCol: number
|
||||
lastCol: number
|
||||
}
|
||||
|
||||
export interface DayTableCell {
|
||||
key: string // probably just the serialized date, but could be other metadata if this col is specific to another entity
|
||||
date: DateMarker
|
||||
extraRenderProps?: Dictionary
|
||||
extraDataAttrs?: Dictionary
|
||||
extraClassNames?: string[]
|
||||
extraDateSpan?: Dictionary
|
||||
}
|
||||
|
||||
export class DayTableModel {
|
||||
rowCnt: number
|
||||
colCnt: number
|
||||
cells: DayTableCell[][]
|
||||
headerDates: DateMarker[]
|
||||
|
||||
private daySeries: DaySeriesModel
|
||||
|
||||
constructor(daySeries: DaySeriesModel, breakOnWeeks: boolean) {
|
||||
let { dates } = daySeries
|
||||
let daysPerRow
|
||||
let firstDay
|
||||
let rowCnt
|
||||
|
||||
if (breakOnWeeks) {
|
||||
// count columns until the day-of-week repeats
|
||||
firstDay = dates[0].getUTCDay()
|
||||
for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) {
|
||||
if (dates[daysPerRow].getUTCDay() === firstDay) {
|
||||
break
|
||||
}
|
||||
}
|
||||
rowCnt = Math.ceil(dates.length / daysPerRow)
|
||||
} else {
|
||||
rowCnt = 1
|
||||
daysPerRow = dates.length
|
||||
}
|
||||
|
||||
this.rowCnt = rowCnt
|
||||
this.colCnt = daysPerRow
|
||||
this.daySeries = daySeries
|
||||
this.cells = this.buildCells()
|
||||
this.headerDates = this.buildHeaderDates()
|
||||
}
|
||||
|
||||
private buildCells() {
|
||||
let rows = []
|
||||
|
||||
for (let row = 0; row < this.rowCnt; row += 1) {
|
||||
let cells = []
|
||||
|
||||
for (let col = 0; col < this.colCnt; col += 1) {
|
||||
cells.push(
|
||||
this.buildCell(row, col),
|
||||
)
|
||||
}
|
||||
|
||||
rows.push(cells)
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
private buildCell(row, col): DayTableCell {
|
||||
let date = this.daySeries.dates[row * this.colCnt + col]
|
||||
return {
|
||||
key: date.toISOString(),
|
||||
date,
|
||||
}
|
||||
}
|
||||
|
||||
private buildHeaderDates() {
|
||||
let dates = []
|
||||
|
||||
for (let col = 0; col < this.colCnt; col += 1) {
|
||||
dates.push(this.cells[0][col].date)
|
||||
}
|
||||
|
||||
return dates
|
||||
}
|
||||
|
||||
sliceRange(range: DateRange): DayTableSeg[] {
|
||||
let { colCnt } = this
|
||||
let seriesSeg = this.daySeries.sliceRange(range)
|
||||
let segs: DayTableSeg[] = []
|
||||
|
||||
if (seriesSeg) {
|
||||
let { firstIndex, lastIndex } = seriesSeg
|
||||
let index = firstIndex
|
||||
|
||||
while (index <= lastIndex) {
|
||||
let row = Math.floor(index / colCnt)
|
||||
let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1)
|
||||
|
||||
segs.push({
|
||||
row,
|
||||
firstCol: index % colCnt,
|
||||
lastCol: (nextIndex - 1) % colCnt,
|
||||
isStart: seriesSeg.isStart && index === firstIndex,
|
||||
isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex,
|
||||
})
|
||||
|
||||
index = nextIndex
|
||||
}
|
||||
}
|
||||
|
||||
return segs
|
||||
}
|
||||
}
|
59
fullcalendar-main/packages/core/src/common/Emitter.ts
Normal file
59
fullcalendar-main/packages/core/src/common/Emitter.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
export interface HandlerFuncTypeHash {
|
||||
[eventName: string]: (...args: any[]) => any // with all properties required
|
||||
}
|
||||
|
||||
export class Emitter<HandlerFuncs extends HandlerFuncTypeHash> {
|
||||
private handlers: { [Prop in keyof HandlerFuncs]?: HandlerFuncs[Prop][] } = {}
|
||||
|
||||
private options: Partial<HandlerFuncs>
|
||||
|
||||
private thisContext: any = null
|
||||
|
||||
setThisContext(thisContext) {
|
||||
this.thisContext = thisContext
|
||||
}
|
||||
|
||||
setOptions(options: Partial<HandlerFuncs>) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
on<Prop extends keyof HandlerFuncs>(type: Prop, handler: HandlerFuncs[Prop]) {
|
||||
addToHash(this.handlers, type, handler)
|
||||
}
|
||||
|
||||
off<Prop extends keyof HandlerFuncs>(type: Prop, handler?: HandlerFuncs[Prop]) {
|
||||
removeFromHash(this.handlers, type, handler)
|
||||
}
|
||||
|
||||
trigger<Prop extends keyof HandlerFuncs>(type: Prop, ...args: Parameters<HandlerFuncs[Prop]>) {
|
||||
let attachedHandlers = this.handlers[type] || []
|
||||
let optionHandler = this.options && this.options[type]
|
||||
let handlers = [].concat(optionHandler || [], attachedHandlers)
|
||||
|
||||
for (let handler of handlers) {
|
||||
handler.apply(this.thisContext, args)
|
||||
}
|
||||
}
|
||||
|
||||
hasHandlers(type: keyof HandlerFuncs): boolean {
|
||||
return Boolean(
|
||||
(this.handlers[type] && this.handlers[type].length) ||
|
||||
(this.options && this.options[type]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function addToHash(hash, type, handler) {
|
||||
(hash[type] || (hash[type] = []))
|
||||
.push(handler)
|
||||
}
|
||||
|
||||
function removeFromHash(hash, type, handler?) {
|
||||
if (handler) {
|
||||
if (hash[type]) {
|
||||
hash[type] = hash[type].filter((func) => func !== handler)
|
||||
}
|
||||
} else {
|
||||
delete hash[type] // remove all handler funcs for this type
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import { ComponentChild, createElement } from '../preact.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { Seg } from '../component/DateComponent.js'
|
||||
import { EventImpl } from '../api/EventImpl.js'
|
||||
import {
|
||||
computeSegDraggable,
|
||||
computeSegStartResizable,
|
||||
computeSegEndResizable,
|
||||
EventContentArg,
|
||||
getEventClassNames,
|
||||
setElSeg,
|
||||
} from '../component/event-rendering.js'
|
||||
import { ContentContainer, InnerContainerFunc } from '../content-inject/ContentContainer.js'
|
||||
import { ElProps } from '../content-inject/ContentInjector.js'
|
||||
|
||||
export interface MinimalEventProps {
|
||||
seg: Seg
|
||||
isDragging: boolean // rename to isMirrorDragging? make optional?
|
||||
isResizing: boolean // rename to isMirrorResizing? make optional?
|
||||
isDateSelecting: boolean // rename to isMirrorDateSelecting? make optional?
|
||||
isSelected: boolean
|
||||
isPast: boolean
|
||||
isFuture: boolean
|
||||
isToday: boolean
|
||||
}
|
||||
|
||||
export type EventContainerProps = ElProps & MinimalEventProps & {
|
||||
defaultGenerator: (renderProps: EventContentArg) => ComponentChild
|
||||
disableDragging?: boolean
|
||||
disableResizing?: boolean
|
||||
timeText: string
|
||||
children?: InnerContainerFunc<EventContentArg>
|
||||
}
|
||||
|
||||
export class EventContainer extends BaseComponent<EventContainerProps> {
|
||||
el: HTMLElement
|
||||
|
||||
render() {
|
||||
const { props, context } = this
|
||||
const { options } = context
|
||||
const { seg } = props
|
||||
const { eventRange } = seg
|
||||
const { ui } = eventRange
|
||||
|
||||
const renderProps: EventContentArg = {
|
||||
event: new EventImpl(context, eventRange.def, eventRange.instance),
|
||||
view: context.viewApi,
|
||||
timeText: props.timeText,
|
||||
textColor: ui.textColor,
|
||||
backgroundColor: ui.backgroundColor,
|
||||
borderColor: ui.borderColor,
|
||||
isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
|
||||
isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
|
||||
isEndResizable: !props.disableResizing && computeSegEndResizable(seg, context),
|
||||
isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
|
||||
isStart: Boolean(seg.isStart),
|
||||
isEnd: Boolean(seg.isEnd),
|
||||
isPast: Boolean(props.isPast), // TODO: don't cast. getDateMeta does it
|
||||
isFuture: Boolean(props.isFuture), // TODO: don't cast. getDateMeta does it
|
||||
isToday: Boolean(props.isToday), // TODO: don't cast. getDateMeta does it
|
||||
isSelected: Boolean(props.isSelected),
|
||||
isDragging: Boolean(props.isDragging),
|
||||
isResizing: Boolean(props.isResizing),
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
{...props /* contains children */}
|
||||
elRef={this.handleEl}
|
||||
elClasses={[
|
||||
...getEventClassNames(renderProps),
|
||||
...seg.eventRange.ui.classNames,
|
||||
...(props.elClasses || []),
|
||||
]}
|
||||
renderProps={renderProps}
|
||||
generatorName="eventContent"
|
||||
customGenerator={options.eventContent}
|
||||
defaultGenerator={props.defaultGenerator}
|
||||
classNameGenerator={options.eventClassNames}
|
||||
didMount={options.eventDidMount}
|
||||
willUnmount={options.eventWillUnmount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
handleEl = (el: HTMLElement | null) => {
|
||||
this.el = el
|
||||
|
||||
if (el) {
|
||||
setElSeg(el, this.props.seg)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: EventContainerProps): void {
|
||||
if (this.el && this.props.seg !== prevProps.seg) {
|
||||
setElSeg(this.el, this.props.seg)
|
||||
}
|
||||
}
|
||||
}
|
229
fullcalendar-main/packages/core/src/common/MoreLinkContainer.tsx
Normal file
229
fullcalendar-main/packages/core/src/common/MoreLinkContainer.tsx
Normal file
|
@ -0,0 +1,229 @@
|
|||
import { EventImpl } from '../api/EventImpl.js'
|
||||
import { Seg } from '../component/DateComponent.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { addDays, DateMarker } from '../datelib/marker.js'
|
||||
import { DateProfile } from '../DateProfileGenerator.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
import { elementClosest, getUniqueDomId } from '../util/dom-manip.js'
|
||||
import { formatWithOrdinals } from '../util/misc.js'
|
||||
import { createElement, Fragment, ComponentChild, RefObject } from '../preact.js'
|
||||
import { BaseComponent, setRef } from '../vdom-util.js'
|
||||
import { ViewApi } from '../api/ViewApi.js'
|
||||
import { ViewContext, ViewContextType } from '../ViewContext.js'
|
||||
import { MorePopover } from './MorePopover.js'
|
||||
import { MountArg } from './render-hook.js'
|
||||
import { ContentContainer, InnerContainerFunc } from '../content-inject/ContentContainer.js'
|
||||
import { ElProps } from '../content-inject/ContentInjector.js'
|
||||
import { createAriaClickAttrs } from '../util/dom-event.js'
|
||||
|
||||
export interface MoreLinkContainerProps extends Partial<ElProps> {
|
||||
dateProfile: DateProfile
|
||||
todayRange: DateRange
|
||||
allDayDate: DateMarker | null
|
||||
moreCnt: number // can't always derive from hiddenSegs. some hiddenSegs might be due to lack of dimensions
|
||||
allSegs: Seg[]
|
||||
hiddenSegs: Seg[]
|
||||
extraDateSpan?: Dictionary
|
||||
alignmentElRef?: RefObject<HTMLElement> // will use internal <a> if unspecified
|
||||
alignGridTop?: boolean // for popover
|
||||
forceTimed?: boolean // for popover
|
||||
popoverContent: () => ComponentChild
|
||||
defaultGenerator?: (renderProps: MoreLinkContentArg) => ComponentChild
|
||||
children?: InnerContainerFunc<MoreLinkContentArg>
|
||||
}
|
||||
|
||||
export interface MoreLinkContentArg {
|
||||
num: number
|
||||
text: string
|
||||
shortText: string
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export type MoreLinkMountArg = MountArg<MoreLinkContentArg>
|
||||
|
||||
interface MoreLinkContainerState {
|
||||
isPopoverOpen: boolean
|
||||
popoverId: string
|
||||
}
|
||||
|
||||
export class MoreLinkContainer extends BaseComponent<MoreLinkContainerProps, MoreLinkContainerState> {
|
||||
private linkEl: HTMLElement
|
||||
private parentEl: HTMLElement
|
||||
|
||||
state = {
|
||||
isPopoverOpen: false,
|
||||
popoverId: getUniqueDomId(),
|
||||
}
|
||||
|
||||
render() {
|
||||
let { props, state } = this
|
||||
return (
|
||||
<ViewContextType.Consumer>
|
||||
{(context: ViewContext) => {
|
||||
let { viewApi, options, calendarApi } = context
|
||||
let { moreLinkText } = options
|
||||
let { moreCnt } = props
|
||||
let range = computeRange(props)
|
||||
|
||||
let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
|
||||
? moreLinkText.call(calendarApi, moreCnt)
|
||||
: `+${moreCnt} ${moreLinkText}`
|
||||
let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text)
|
||||
|
||||
let renderProps: MoreLinkContentArg = {
|
||||
num: moreCnt,
|
||||
shortText: `+${moreCnt}`, // TODO: offer hook or i18n?
|
||||
text,
|
||||
view: viewApi,
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{Boolean(props.moreCnt) && (
|
||||
<ContentContainer
|
||||
elTag={props.elTag || 'a'}
|
||||
elRef={this.handleLinkEl}
|
||||
elClasses={[
|
||||
...(props.elClasses || []),
|
||||
'fc-more-link',
|
||||
]}
|
||||
elStyle={props.elStyle}
|
||||
elAttrs={{
|
||||
...props.elAttrs,
|
||||
...createAriaClickAttrs(this.handleClick),
|
||||
title: hint,
|
||||
'aria-expanded': state.isPopoverOpen,
|
||||
'aria-controls': state.isPopoverOpen ? state.popoverId : '',
|
||||
}}
|
||||
renderProps={renderProps}
|
||||
generatorName="moreLinkContent"
|
||||
customGenerator={options.moreLinkContent}
|
||||
defaultGenerator={props.defaultGenerator || renderMoreLinkInner}
|
||||
classNameGenerator={options.moreLinkClassNames}
|
||||
didMount={options.moreLinkDidMount}
|
||||
willUnmount={options.moreLinkWillUnmount}
|
||||
>{props.children}</ContentContainer>
|
||||
)}
|
||||
{state.isPopoverOpen && (
|
||||
<MorePopover
|
||||
id={state.popoverId}
|
||||
startDate={range.start}
|
||||
endDate={range.end}
|
||||
dateProfile={props.dateProfile}
|
||||
todayRange={props.todayRange}
|
||||
extraDateSpan={props.extraDateSpan}
|
||||
parentEl={this.parentEl}
|
||||
alignmentEl={
|
||||
props.alignmentElRef ?
|
||||
props.alignmentElRef.current :
|
||||
this.linkEl
|
||||
}
|
||||
alignGridTop={props.alignGridTop}
|
||||
forceTimed={props.forceTimed}
|
||||
onClose={this.handlePopoverClose}
|
||||
>
|
||||
{props.popoverContent()}
|
||||
</MorePopover>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
</ViewContextType.Consumer>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateParentEl()
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateParentEl()
|
||||
}
|
||||
|
||||
handleLinkEl = (linkEl: HTMLElement | null) => {
|
||||
this.linkEl = linkEl
|
||||
|
||||
if (this.props.elRef) {
|
||||
setRef(this.props.elRef, linkEl)
|
||||
}
|
||||
}
|
||||
|
||||
updateParentEl() {
|
||||
if (this.linkEl) {
|
||||
this.parentEl = elementClosest(this.linkEl, '.fc-view-harness')
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = (ev: MouseEvent) => {
|
||||
let { props, context } = this
|
||||
let { moreLinkClick } = context.options
|
||||
let date = computeRange(props).start
|
||||
|
||||
function buildPublicSeg(seg: Seg) {
|
||||
let { def, instance, range } = seg.eventRange
|
||||
return {
|
||||
event: new EventImpl(context, def, instance),
|
||||
start: context.dateEnv.toDate(range.start),
|
||||
end: context.dateEnv.toDate(range.end),
|
||||
isStart: seg.isStart,
|
||||
isEnd: seg.isEnd,
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof moreLinkClick === 'function') {
|
||||
moreLinkClick = moreLinkClick({
|
||||
date,
|
||||
allDay: Boolean(props.allDayDate),
|
||||
allSegs: props.allSegs.map(buildPublicSeg),
|
||||
hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
|
||||
jsEvent: ev,
|
||||
view: context.viewApi,
|
||||
}) as string | undefined
|
||||
}
|
||||
|
||||
if (!moreLinkClick || moreLinkClick === 'popover') {
|
||||
this.setState({ isPopoverOpen: true })
|
||||
} else if (typeof moreLinkClick === 'string') { // a view name
|
||||
context.calendarApi.zoomTo(date, moreLinkClick)
|
||||
}
|
||||
}
|
||||
|
||||
handlePopoverClose = () => {
|
||||
this.setState({ isPopoverOpen: false })
|
||||
}
|
||||
}
|
||||
|
||||
function renderMoreLinkInner(props: MoreLinkContentArg) {
|
||||
return props.text
|
||||
}
|
||||
|
||||
function computeRange(props: MoreLinkContainerProps): DateRange {
|
||||
if (props.allDayDate) {
|
||||
return {
|
||||
start: props.allDayDate,
|
||||
end: addDays(props.allDayDate, 1),
|
||||
}
|
||||
}
|
||||
|
||||
let { hiddenSegs } = props
|
||||
return {
|
||||
start: computeEarliestSegStart(hiddenSegs),
|
||||
end: computeLatestSegEnd(hiddenSegs),
|
||||
}
|
||||
}
|
||||
|
||||
export function computeEarliestSegStart(segs: Seg[]): DateMarker {
|
||||
return segs.reduce(pickEarliestStart).eventRange.range.start
|
||||
}
|
||||
|
||||
function pickEarliestStart(seg0: Seg, seg1: Seg): Seg {
|
||||
return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1
|
||||
}
|
||||
|
||||
function computeLatestSegEnd(segs: Seg[]): DateMarker {
|
||||
return segs.reduce(pickLatestEnd).eventRange.range.end
|
||||
}
|
||||
|
||||
function pickLatestEnd(seg0: Seg, seg1: Seg): Seg {
|
||||
return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1
|
||||
}
|
114
fullcalendar-main/packages/core/src/common/MorePopover.tsx
Normal file
114
fullcalendar-main/packages/core/src/common/MorePopover.tsx
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { DateComponent } from '../component/DateComponent.js'
|
||||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { DateProfile } from '../DateProfileGenerator.js'
|
||||
import { Hit } from '../interactions/hit.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
import { createElement, ComponentChildren } from '../preact.js'
|
||||
import { DayCellContainer, hasCustomDayCellContent } from './DayCellContainer.js'
|
||||
import { Popover } from './Popover.js'
|
||||
|
||||
export interface MorePopoverProps {
|
||||
id: string
|
||||
startDate: DateMarker
|
||||
endDate: DateMarker
|
||||
dateProfile: DateProfile
|
||||
parentEl: HTMLElement
|
||||
alignmentEl: HTMLElement
|
||||
alignGridTop?: boolean
|
||||
forceTimed?: boolean
|
||||
todayRange: DateRange
|
||||
extraDateSpan: Dictionary
|
||||
children: ComponentChildren
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export class MorePopover extends DateComponent<MorePopoverProps> {
|
||||
rootEl: HTMLElement
|
||||
|
||||
render() {
|
||||
let { options, dateEnv } = this.context
|
||||
let { props } = this
|
||||
let { startDate, todayRange, dateProfile } = props
|
||||
let title = dateEnv.format(startDate, options.dayPopoverFormat)
|
||||
|
||||
return (
|
||||
<DayCellContainer
|
||||
elRef={this.handleRootEl}
|
||||
date={startDate}
|
||||
dateProfile={dateProfile}
|
||||
todayRange={todayRange}
|
||||
>
|
||||
{(InnerContent, renderProps, elAttrs) => (
|
||||
<Popover
|
||||
elRef={elAttrs.ref}
|
||||
id={props.id}
|
||||
title={title}
|
||||
extraClassNames={
|
||||
['fc-more-popover'].concat(
|
||||
(elAttrs.className as (string | undefined)) || [],
|
||||
)
|
||||
}
|
||||
extraAttrs={elAttrs /* TODO: make these time-based when not whole-day? */}
|
||||
parentEl={props.parentEl}
|
||||
alignmentEl={props.alignmentEl}
|
||||
alignGridTop={props.alignGridTop}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
{hasCustomDayCellContent(options) && (
|
||||
<InnerContent
|
||||
elTag="div"
|
||||
elClasses={['fc-more-popover-misc']}
|
||||
/>
|
||||
)}
|
||||
{props.children}
|
||||
</Popover>
|
||||
)}
|
||||
</DayCellContainer>
|
||||
)
|
||||
}
|
||||
|
||||
handleRootEl = (rootEl: HTMLElement | null) => {
|
||||
this.rootEl = rootEl
|
||||
|
||||
if (rootEl) {
|
||||
this.context.registerInteractiveComponent(this, {
|
||||
el: rootEl,
|
||||
useEventCenter: false,
|
||||
})
|
||||
} else {
|
||||
this.context.unregisterInteractiveComponent(this)
|
||||
}
|
||||
}
|
||||
|
||||
queryHit(positionLeft: number, positionTop: number, elWidth: number, elHeight: number): Hit {
|
||||
let { rootEl, props } = this
|
||||
|
||||
if (
|
||||
positionLeft >= 0 && positionLeft < elWidth &&
|
||||
positionTop >= 0 && positionTop < elHeight
|
||||
) {
|
||||
return {
|
||||
dateProfile: props.dateProfile,
|
||||
dateSpan: {
|
||||
allDay: !props.forceTimed,
|
||||
range: {
|
||||
start: props.startDate,
|
||||
end: props.endDate,
|
||||
},
|
||||
...props.extraDateSpan,
|
||||
},
|
||||
dayEl: rootEl,
|
||||
rect: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: elWidth,
|
||||
bottom: elHeight,
|
||||
},
|
||||
layer: 1, // important when comparing with hits from other components
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { MountArg } from './render-hook.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { ViewContext, ViewContextType } from '../ViewContext.js'
|
||||
import { createElement } from '../preact.js'
|
||||
import { ViewApi } from '../api/ViewApi.js'
|
||||
import { ElProps } from '../content-inject/ContentInjector.js'
|
||||
import { InnerContainerFunc, ContentContainer } from '../content-inject/ContentContainer.js'
|
||||
|
||||
export interface NowIndicatorContainerProps extends Partial<ElProps> {
|
||||
isAxis: boolean
|
||||
date: DateMarker
|
||||
children?: InnerContainerFunc<NowIndicatorContentArg>
|
||||
}
|
||||
|
||||
export interface NowIndicatorContentArg {
|
||||
isAxis: boolean
|
||||
date: Date
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export type NowIndicatorMountArg = MountArg<NowIndicatorContentArg>
|
||||
|
||||
export const NowIndicatorContainer = (props: NowIndicatorContainerProps) => (
|
||||
<ViewContextType.Consumer>
|
||||
{(context: ViewContext) => {
|
||||
let { options } = context
|
||||
let renderProps: NowIndicatorContentArg = {
|
||||
isAxis: props.isAxis,
|
||||
date: context.dateEnv.toDate(props.date),
|
||||
view: context.viewApi,
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
{...props /* includes children */}
|
||||
elTag={props.elTag || 'div'}
|
||||
renderProps={renderProps}
|
||||
generatorName="nowIndicatorContent"
|
||||
customGenerator={options.nowIndicatorContent}
|
||||
classNameGenerator={options.nowIndicatorClassNames}
|
||||
didMount={options.nowIndicatorDidMount}
|
||||
willUnmount={options.nowIndicatorWillUnmount}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ViewContextType.Consumer>
|
||||
)
|
132
fullcalendar-main/packages/core/src/common/Popover.tsx
Normal file
132
fullcalendar-main/packages/core/src/common/Popover.tsx
Normal file
|
@ -0,0 +1,132 @@
|
|||
import { Dictionary } from '../options.js'
|
||||
import { computeClippedClientRect } from '../util/dom-geom.js'
|
||||
import { applyStyle, elementClosest, getEventTargetViaRoot, getUniqueDomId } from '../util/dom-manip.js'
|
||||
import { createElement, ComponentChildren, Ref, createPortal } from '../preact.js'
|
||||
import { BaseComponent, setRef } from '../vdom-util.js'
|
||||
|
||||
export interface PopoverProps {
|
||||
elRef?: Ref<HTMLElement>
|
||||
id: string
|
||||
title: string
|
||||
extraClassNames?: string[]
|
||||
extraAttrs?: Dictionary
|
||||
parentEl: HTMLElement
|
||||
alignmentEl: HTMLElement
|
||||
alignGridTop?: boolean
|
||||
children?: ComponentChildren
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const PADDING_FROM_VIEWPORT = 10
|
||||
|
||||
export class Popover extends BaseComponent<PopoverProps> {
|
||||
private rootEl: HTMLElement
|
||||
state = {
|
||||
titleId: getUniqueDomId(),
|
||||
}
|
||||
|
||||
render(): any {
|
||||
let { theme, options } = this.context
|
||||
let { props, state } = this
|
||||
let classNames = [
|
||||
'fc-popover',
|
||||
theme.getClass('popover'),
|
||||
].concat(
|
||||
props.extraClassNames || [],
|
||||
)
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
{...props.extraAttrs}
|
||||
id={props.id}
|
||||
className={classNames.join(' ')}
|
||||
aria-labelledby={state.titleId}
|
||||
ref={this.handleRootEl}
|
||||
>
|
||||
<div className={'fc-popover-header ' + theme.getClass('popoverHeader')}>
|
||||
<span className="fc-popover-title" id={state.titleId}>
|
||||
{props.title}
|
||||
</span>
|
||||
<span
|
||||
className={'fc-popover-close ' + theme.getIconClass('close')}
|
||||
title={options.closeHint}
|
||||
onClick={this.handleCloseClick}
|
||||
/>
|
||||
</div>
|
||||
<div className={'fc-popover-body ' + theme.getClass('popoverContent')}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>,
|
||||
props.parentEl,
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', this.handleDocumentMouseDown)
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown)
|
||||
this.updateSize()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleDocumentMouseDown)
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown)
|
||||
}
|
||||
|
||||
handleRootEl = (el: HTMLElement | null) => {
|
||||
this.rootEl = el
|
||||
|
||||
if (this.props.elRef) {
|
||||
setRef(this.props.elRef, el)
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered when the user clicks *anywhere* in the document, for the autoHide feature
|
||||
handleDocumentMouseDown = (ev) => {
|
||||
// only hide the popover if the click happened outside the popover
|
||||
const target = getEventTargetViaRoot(ev) as HTMLElement
|
||||
if (!this.rootEl.contains(target)) {
|
||||
this.handleCloseClick()
|
||||
}
|
||||
}
|
||||
|
||||
handleDocumentKeyDown = (ev) => {
|
||||
if (ev.key === 'Escape') {
|
||||
this.handleCloseClick()
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseClick = () => {
|
||||
let { onClose } = this.props
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
private updateSize() {
|
||||
let { isRtl } = this.context
|
||||
let { alignmentEl, alignGridTop } = this.props
|
||||
let { rootEl } = this
|
||||
|
||||
let alignmentRect = computeClippedClientRect(alignmentEl)
|
||||
if (alignmentRect) {
|
||||
let popoverDims = rootEl.getBoundingClientRect()
|
||||
|
||||
// position relative to viewport
|
||||
let popoverTop = alignGridTop
|
||||
? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
|
||||
: alignmentRect.top
|
||||
let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left
|
||||
|
||||
// constrain
|
||||
popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT)
|
||||
popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width)
|
||||
popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT)
|
||||
|
||||
let origin = rootEl.offsetParent.getBoundingClientRect()
|
||||
applyStyle(rootEl, {
|
||||
top: popoverTop - origin.top,
|
||||
left: popoverLeft - origin.left,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
125
fullcalendar-main/packages/core/src/common/PositionCache.ts
Normal file
125
fullcalendar-main/packages/core/src/common/PositionCache.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Records offset information for a set of elements, relative to an origin element.
|
||||
Can record the left/right OR the top/bottom OR both.
|
||||
Provides methods for querying the cache by position.
|
||||
*/
|
||||
export class PositionCache {
|
||||
els: HTMLElement[] // assumed to be siblings
|
||||
originClientRect: ClientRect
|
||||
|
||||
// arrays of coordinates (from topleft of originEl)
|
||||
// caller can access these directly
|
||||
lefts: any
|
||||
rights: any
|
||||
tops: any
|
||||
bottoms: any
|
||||
|
||||
constructor(originEl: HTMLElement, els: HTMLElement[], isHorizontal: boolean, isVertical: boolean) {
|
||||
this.els = els
|
||||
|
||||
let originClientRect = this.originClientRect = originEl.getBoundingClientRect() // relative to viewport top-left
|
||||
|
||||
if (isHorizontal) {
|
||||
this.buildElHorizontals(originClientRect.left)
|
||||
}
|
||||
|
||||
if (isVertical) {
|
||||
this.buildElVerticals(originClientRect.top)
|
||||
}
|
||||
}
|
||||
|
||||
// Populates the left/right internal coordinate arrays
|
||||
buildElHorizontals(originClientLeft: number) {
|
||||
let lefts = []
|
||||
let rights = []
|
||||
|
||||
for (let el of this.els) {
|
||||
let rect = el.getBoundingClientRect()
|
||||
lefts.push(rect.left - originClientLeft)
|
||||
rights.push(rect.right - originClientLeft)
|
||||
}
|
||||
|
||||
this.lefts = lefts
|
||||
this.rights = rights
|
||||
}
|
||||
|
||||
// Populates the top/bottom internal coordinate arrays
|
||||
buildElVerticals(originClientTop: number) {
|
||||
let tops = []
|
||||
let bottoms = []
|
||||
|
||||
for (let el of this.els) {
|
||||
let rect = el.getBoundingClientRect()
|
||||
tops.push(rect.top - originClientTop)
|
||||
bottoms.push(rect.bottom - originClientTop)
|
||||
}
|
||||
|
||||
this.tops = tops
|
||||
this.bottoms = bottoms
|
||||
}
|
||||
|
||||
// Given a left offset (from document left), returns the index of the el that it horizontally intersects.
|
||||
// If no intersection is made, returns undefined.
|
||||
leftToIndex(leftPosition: number) {
|
||||
let { lefts, rights } = this
|
||||
let len = lefts.length
|
||||
let i
|
||||
|
||||
for (i = 0; i < len; i += 1) {
|
||||
if (leftPosition >= lefts[i] && leftPosition < rights[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return undefined // TODO: better
|
||||
}
|
||||
|
||||
// Given a top offset (from document top), returns the index of the el that it vertically intersects.
|
||||
// If no intersection is made, returns undefined.
|
||||
topToIndex(topPosition: number) {
|
||||
let { tops, bottoms } = this
|
||||
let len = tops.length
|
||||
let i
|
||||
|
||||
for (i = 0; i < len; i += 1) {
|
||||
if (topPosition >= tops[i] && topPosition < bottoms[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return undefined // TODO: better
|
||||
}
|
||||
|
||||
// Gets the width of the element at the given index
|
||||
getWidth(leftIndex: number) {
|
||||
return this.rights[leftIndex] - this.lefts[leftIndex]
|
||||
}
|
||||
|
||||
// Gets the height of the element at the given index
|
||||
getHeight(topIndex: number) {
|
||||
return this.bottoms[topIndex] - this.tops[topIndex]
|
||||
}
|
||||
|
||||
similarTo(otherCache: PositionCache) {
|
||||
return similarNumArrays(this.tops || [], otherCache.tops || []) &&
|
||||
similarNumArrays(this.bottoms || [], otherCache.bottoms || []) &&
|
||||
similarNumArrays(this.lefts || [], otherCache.lefts || []) &&
|
||||
similarNumArrays(this.rights || [], otherCache.rights || [])
|
||||
}
|
||||
}
|
||||
|
||||
function similarNumArrays(a: number[], b: number[]): boolean {
|
||||
const len = a.length
|
||||
|
||||
if (len !== b.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (Math.round(a[i]) !== Math.round(b[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
88
fullcalendar-main/packages/core/src/common/StandardEvent.tsx
Normal file
88
fullcalendar-main/packages/core/src/common/StandardEvent.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { createElement, Fragment } from '../preact.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { buildSegTimeText, EventContentArg, getSegAnchorAttrs } from '../component/event-rendering.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
import { EventContainer } from './EventContainer.js'
|
||||
import { Seg } from '../component/DateComponent.js'
|
||||
import { ElRef } from '../content-inject/ContentInjector.js'
|
||||
|
||||
export interface StandardEventProps {
|
||||
elRef?: ElRef
|
||||
elClasses?: string[]
|
||||
seg: Seg
|
||||
isDragging: boolean // rename to isMirrorDragging? make optional?
|
||||
isResizing: boolean // rename to isMirrorResizing? make optional?
|
||||
isDateSelecting: boolean // rename to isMirrorDateSelecting? make optional?
|
||||
isSelected: boolean
|
||||
isPast: boolean
|
||||
isFuture: boolean
|
||||
isToday: boolean
|
||||
disableDragging?: boolean // defaults false
|
||||
disableResizing?: boolean // defaults false
|
||||
defaultTimeFormat: DateFormatter
|
||||
defaultDisplayEventTime?: boolean // default true
|
||||
defaultDisplayEventEnd?: boolean // default true
|
||||
}
|
||||
|
||||
// should not be a purecomponent
|
||||
export class StandardEvent extends BaseComponent<StandardEventProps> {
|
||||
render() {
|
||||
let { props, context } = this
|
||||
let { options } = context
|
||||
let { seg } = props
|
||||
let { ui } = seg.eventRange
|
||||
let timeFormat = options.eventTimeFormat || props.defaultTimeFormat
|
||||
let timeText = buildSegTimeText(
|
||||
seg,
|
||||
timeFormat,
|
||||
context,
|
||||
props.defaultDisplayEventTime,
|
||||
props.defaultDisplayEventEnd,
|
||||
)
|
||||
|
||||
return (
|
||||
<EventContainer
|
||||
{...props /* includes elRef */}
|
||||
elTag="a"
|
||||
elStyle={{
|
||||
borderColor: ui.borderColor,
|
||||
backgroundColor: ui.backgroundColor,
|
||||
}}
|
||||
elAttrs={getSegAnchorAttrs(seg, context)}
|
||||
defaultGenerator={renderInnerContent}
|
||||
timeText={timeText}
|
||||
>
|
||||
{(InnerContent, eventContentArg) => (
|
||||
<Fragment>
|
||||
<InnerContent
|
||||
elTag="div"
|
||||
elClasses={['fc-event-main']}
|
||||
elStyle={{ color: eventContentArg.textColor }}
|
||||
/>
|
||||
{Boolean(eventContentArg.isStartResizable) && (
|
||||
<div className="fc-event-resizer fc-event-resizer-start" />
|
||||
)}
|
||||
{Boolean(eventContentArg.isEndResizable) && (
|
||||
<div className="fc-event-resizer fc-event-resizer-end" />
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</EventContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function renderInnerContent(innerProps: EventContentArg) {
|
||||
return (
|
||||
<div className="fc-event-main-frame">
|
||||
{innerProps.timeText && (
|
||||
<div className="fc-event-time">{innerProps.timeText}</div>
|
||||
)}
|
||||
<div className="fc-event-title-container">
|
||||
<div className="fc-event-title fc-sticky">
|
||||
{innerProps.event.title || <Fragment> </Fragment>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
88
fullcalendar-main/packages/core/src/common/TableDateCell.tsx
Normal file
88
fullcalendar-main/packages/core/src/common/TableDateCell.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { DateRange } from '../datelib/date-range.js'
|
||||
import { getDayClassNames, getDateMeta } from '../component/date-rendering.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { createElement } from '../preact.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
import { formatDayString } from '../datelib/formatting-utils.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { buildNavLinkAttrs } from './nav-link.js'
|
||||
import { DateProfile } from '../DateProfileGenerator.js'
|
||||
import { DayHeaderContentArg } from '../render-hook-misc.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
import { CLASS_NAME, renderInner } from './table-cell-util.js'
|
||||
import { ContentContainer } from '../content-inject/ContentContainer.js'
|
||||
|
||||
export interface TableDateCellProps {
|
||||
date: DateMarker
|
||||
dateProfile: DateProfile
|
||||
todayRange: DateRange
|
||||
colCnt: number
|
||||
dayHeaderFormat: DateFormatter
|
||||
colSpan?: number
|
||||
isSticky?: boolean // TODO: get this outta here somehow
|
||||
extraDataAttrs?: Dictionary
|
||||
extraRenderProps?: Dictionary
|
||||
}
|
||||
|
||||
// BAD name for this class now. used in the Header
|
||||
export class TableDateCell extends BaseComponent<TableDateCellProps> {
|
||||
render() {
|
||||
let { dateEnv, options, theme, viewApi } = this.context
|
||||
let { props } = this
|
||||
let { date, dateProfile } = props
|
||||
let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile)
|
||||
|
||||
let classNames = [CLASS_NAME].concat(
|
||||
getDayClassNames(dayMeta, theme),
|
||||
)
|
||||
let text = dateEnv.format(date, props.dayHeaderFormat)
|
||||
|
||||
// if colCnt is 1, we are already in a day-view and don't need a navlink
|
||||
let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1)
|
||||
? buildNavLinkAttrs(this.context, date)
|
||||
: {}
|
||||
|
||||
let renderProps: DayHeaderContentArg = {
|
||||
date: dateEnv.toDate(date),
|
||||
view: viewApi,
|
||||
...props.extraRenderProps,
|
||||
text,
|
||||
...dayMeta,
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
elTag="th"
|
||||
elClasses={classNames}
|
||||
elAttrs={{
|
||||
role: 'columnheader',
|
||||
colSpan: props.colSpan,
|
||||
'data-date': !dayMeta.isDisabled ? formatDayString(date) : undefined,
|
||||
...props.extraDataAttrs,
|
||||
}}
|
||||
renderProps={renderProps}
|
||||
generatorName="dayHeaderContent"
|
||||
customGenerator={options.dayHeaderContent}
|
||||
defaultGenerator={renderInner}
|
||||
classNameGenerator={options.dayHeaderClassNames}
|
||||
didMount={options.dayHeaderDidMount}
|
||||
willUnmount={options.dayHeaderWillUnmount}
|
||||
>
|
||||
{(InnerContainer) => (
|
||||
<div className="fc-scrollgrid-sync-inner">
|
||||
{!dayMeta.isDisabled && (
|
||||
<InnerContainer
|
||||
elTag="a"
|
||||
elAttrs={navLinkAttrs}
|
||||
elClasses={[
|
||||
'fc-col-header-cell-cushion',
|
||||
props.isSticky && 'fc-sticky',
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
}
|
84
fullcalendar-main/packages/core/src/common/TableDowCell.tsx
Normal file
84
fullcalendar-main/packages/core/src/common/TableDowCell.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { getDayClassNames, DateMeta } from '../component/date-rendering.js'
|
||||
import { addDays } from '../datelib/marker.js'
|
||||
import { createElement } from '../preact.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { Dictionary } from '../options.js'
|
||||
import { CLASS_NAME, renderInner } from './table-cell-util.js'
|
||||
import { DayHeaderContentArg } from '../render-hook-misc.js'
|
||||
import { createFormatter } from '../datelib/formatting.js'
|
||||
import { ContentContainer } from '../content-inject/ContentContainer.js'
|
||||
|
||||
export interface TableDowCellProps {
|
||||
dow: number
|
||||
dayHeaderFormat: DateFormatter
|
||||
colSpan?: number
|
||||
isSticky?: boolean // TODO: get this outta here somehow
|
||||
extraRenderProps?: Dictionary
|
||||
extraDataAttrs?: Dictionary
|
||||
extraClassNames?: string[]
|
||||
}
|
||||
|
||||
const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' })
|
||||
|
||||
export class TableDowCell extends BaseComponent<TableDowCellProps> {
|
||||
render() {
|
||||
let { props } = this
|
||||
let { dateEnv, theme, viewApi, options } = this.context
|
||||
let date = addDays(new Date(259200000), props.dow) // start with Sun, 04 Jan 1970 00:00:00 GMT
|
||||
let dateMeta: DateMeta = {
|
||||
dow: props.dow,
|
||||
isDisabled: false,
|
||||
isFuture: false,
|
||||
isPast: false,
|
||||
isToday: false,
|
||||
isOther: false,
|
||||
}
|
||||
let text = dateEnv.format(date, props.dayHeaderFormat)
|
||||
let renderProps: DayHeaderContentArg = { // TODO: make this public?
|
||||
date,
|
||||
...dateMeta,
|
||||
view: viewApi,
|
||||
...props.extraRenderProps,
|
||||
text,
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
elTag="th"
|
||||
elClasses={[
|
||||
CLASS_NAME,
|
||||
...getDayClassNames(dateMeta, theme),
|
||||
...(props.extraClassNames || []),
|
||||
]}
|
||||
elAttrs={{
|
||||
role: 'columnheader',
|
||||
colSpan: props.colSpan,
|
||||
...props.extraDataAttrs,
|
||||
}}
|
||||
renderProps={renderProps}
|
||||
generatorName="dayHeaderContent"
|
||||
customGenerator={options.dayHeaderContent}
|
||||
defaultGenerator={renderInner}
|
||||
classNameGenerator={options.dayHeaderClassNames}
|
||||
didMount={options.dayHeaderDidMount}
|
||||
willUnmount={options.dayHeaderWillUnmount}
|
||||
>
|
||||
{(InnerContent) => (
|
||||
<div className="fc-scrollgrid-sync-inner">
|
||||
<InnerContent
|
||||
elTag="a"
|
||||
elClasses={[
|
||||
'fc-col-header-cell-cushion',
|
||||
props.isSticky && 'fc-sticky',
|
||||
]}
|
||||
elAttrs={{
|
||||
'aria-label': dateEnv.format(date, WEEKDAY_FORMAT),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
}
|
51
fullcalendar-main/packages/core/src/common/ViewContainer.tsx
Normal file
51
fullcalendar-main/packages/core/src/common/ViewContainer.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { ViewSpec } from '../structs/view-spec.js'
|
||||
import { MountArg } from './render-hook.js'
|
||||
import { ComponentChildren, createElement } from '../preact.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { ViewApi } from '../api/ViewApi.js'
|
||||
import { ContentContainer } from '../content-inject/ContentContainer.js'
|
||||
import { ElProps } from '../content-inject/ContentInjector.js'
|
||||
|
||||
export interface ViewContainerProps extends Partial<ElProps> {
|
||||
viewSpec: ViewSpec
|
||||
children: ComponentChildren
|
||||
}
|
||||
|
||||
export interface ViewContentArg {
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export type ViewMountArg = MountArg<ViewContentArg>
|
||||
|
||||
export class ViewContainer extends BaseComponent<ViewContainerProps> {
|
||||
render() {
|
||||
let { props, context } = this
|
||||
let { options } = context
|
||||
let renderProps: ViewContentArg = { view: context.viewApi }
|
||||
|
||||
return (
|
||||
<ContentContainer
|
||||
{...props}
|
||||
elTag={props.elTag || 'div'}
|
||||
elClasses={[
|
||||
...buildViewClassNames(props.viewSpec),
|
||||
...(props.elClasses || []),
|
||||
]}
|
||||
renderProps={renderProps}
|
||||
classNameGenerator={options.viewClassNames}
|
||||
generatorName={undefined}
|
||||
didMount={options.viewDidMount}
|
||||
willUnmount={options.viewWillUnmount}
|
||||
>
|
||||
{() => props.children}
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function buildViewClassNames(viewSpec: ViewSpec): string[] {
|
||||
return [
|
||||
`fc-${viewSpec.type}-view`,
|
||||
'fc-view',
|
||||
]
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { ViewContext, ViewContextType } from '../ViewContext.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { MountArg } from './render-hook.js'
|
||||
import { createElement } from '../preact.js'
|
||||
import { DateFormatter } from '../datelib/DateFormatter.js'
|
||||
import { ElProps } from '../content-inject/ContentInjector.js'
|
||||
import { ContentContainer, InnerContainerFunc } from '../content-inject/ContentContainer.js'
|
||||
|
||||
export interface WeekNumberContainerProps extends ElProps {
|
||||
date: DateMarker
|
||||
defaultFormat: DateFormatter
|
||||
children?: InnerContainerFunc<WeekNumberContentArg>
|
||||
}
|
||||
|
||||
export interface WeekNumberContentArg {
|
||||
num: number
|
||||
text: string
|
||||
date: Date
|
||||
}
|
||||
|
||||
export type WeekNumberMountArg = MountArg<WeekNumberContentArg>
|
||||
|
||||
export const WeekNumberContainer = (props: WeekNumberContainerProps) => (
|
||||
<ViewContextType.Consumer>
|
||||
{(context: ViewContext) => {
|
||||
let { dateEnv, options } = context
|
||||
let { date } = props
|
||||
let format = options.weekNumberFormat || props.defaultFormat
|
||||
let num = dateEnv.computeWeekNumber(date) // TODO: somehow use for formatting as well?
|
||||
let text = dateEnv.format(date, format)
|
||||
let renderProps: WeekNumberContentArg = { num, text, date }
|
||||
|
||||
return (
|
||||
<ContentContainer // why isn't WeekNumberContentArg being auto-detected?
|
||||
{...props /* includes children */}
|
||||
renderProps={renderProps}
|
||||
generatorName="weekNumberContent"
|
||||
customGenerator={options.weekNumberContent}
|
||||
defaultGenerator={renderInner}
|
||||
classNameGenerator={options.weekNumberClassNames}
|
||||
didMount={options.weekNumberDidMount}
|
||||
willUnmount={options.weekNumberWillUnmount}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ViewContextType.Consumer>
|
||||
)
|
||||
|
||||
function renderInner(innerProps) {
|
||||
return innerProps.text
|
||||
}
|
53
fullcalendar-main/packages/core/src/common/bg-fill.tsx
Normal file
53
fullcalendar-main/packages/core/src/common/bg-fill.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { createElement } from '../preact.js'
|
||||
import { BaseComponent } from '../vdom-util.js'
|
||||
import { Seg } from '../component/DateComponent.js'
|
||||
import { EventContentArg } from '../component/event-rendering.js'
|
||||
import { EventContainer } from './EventContainer.js'
|
||||
|
||||
export interface BgEventProps {
|
||||
seg: Seg
|
||||
isPast: boolean
|
||||
isFuture: boolean
|
||||
isToday: boolean
|
||||
}
|
||||
|
||||
export class BgEvent extends BaseComponent<BgEventProps> {
|
||||
render() {
|
||||
let { props } = this
|
||||
let { seg } = props
|
||||
|
||||
return (
|
||||
<EventContainer
|
||||
elTag="div"
|
||||
elClasses={['fc-bg-event']}
|
||||
elStyle={{ backgroundColor: seg.eventRange.ui.backgroundColor }}
|
||||
defaultGenerator={renderInnerContent}
|
||||
seg={seg}
|
||||
timeText=""
|
||||
isDragging={false}
|
||||
isResizing={false}
|
||||
isDateSelecting={false}
|
||||
isSelected={false}
|
||||
isPast={props.isPast}
|
||||
isFuture={props.isFuture}
|
||||
isToday={props.isToday}
|
||||
disableDragging={true}
|
||||
disableResizing={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function renderInnerContent(props: EventContentArg) {
|
||||
let { title } = props.event
|
||||
|
||||
return title && (
|
||||
<div className="fc-event-title">{props.event.title}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function renderFill(fillType: string) {
|
||||
return (
|
||||
<div className={`fc-${fillType}`} />
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { EventApi } from '../api/EventApi.js'
|
||||
import { ViewApi } from '../api/ViewApi.js'
|
||||
|
||||
export interface EventSegment {
|
||||
event: EventApi
|
||||
start: Date
|
||||
end: Date
|
||||
isStart: boolean
|
||||
isEnd: boolean
|
||||
}
|
||||
|
||||
export type MoreLinkAction = MoreLinkSimpleAction | MoreLinkHandler
|
||||
export type MoreLinkSimpleAction = 'popover' | 'week' | 'day' | 'timeGridWeek' | 'timeGridDay' | string
|
||||
|
||||
export interface MoreLinkArg {
|
||||
date: Date
|
||||
allDay: boolean
|
||||
allSegs: EventSegment[]
|
||||
hiddenSegs: EventSegment[]
|
||||
jsEvent: UIEvent
|
||||
view: ViewApi
|
||||
}
|
||||
|
||||
export type MoreLinkHandler = (arg: MoreLinkArg) => MoreLinkSimpleAction | void
|
48
fullcalendar-main/packages/core/src/common/nav-link.ts
Normal file
48
fullcalendar-main/packages/core/src/common/nav-link.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { createFormatter } from '../datelib/formatting.js'
|
||||
import { DateMarker } from '../datelib/marker.js'
|
||||
import { createAriaClickAttrs } from '../util/dom-event.js'
|
||||
import { formatWithOrdinals } from '../util/misc.js'
|
||||
import { ViewContext } from '../ViewContext.js'
|
||||
|
||||
const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' })
|
||||
const WEEK_FORMAT = createFormatter({ week: 'long' })
|
||||
|
||||
export function buildNavLinkAttrs(
|
||||
context: ViewContext,
|
||||
dateMarker: DateMarker,
|
||||
viewType = 'day',
|
||||
isTabbable = true,
|
||||
) {
|
||||
const { dateEnv, options, calendarApi } = context
|
||||
let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT)
|
||||
|
||||
if (options.navLinks) {
|
||||
let zonedDate = dateEnv.toDate(dateMarker)
|
||||
|
||||
const handleInteraction = (ev: UIEvent) => {
|
||||
let customAction =
|
||||
viewType === 'day' ? options.navLinkDayClick :
|
||||
viewType === 'week' ? options.navLinkWeekClick : null
|
||||
|
||||
if (typeof customAction === 'function') {
|
||||
customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev)
|
||||
} else {
|
||||
if (typeof customAction === 'string') {
|
||||
viewType = customAction
|
||||
}
|
||||
calendarApi.zoomTo(dateMarker, viewType)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr),
|
||||
'data-navlink': '', // for legacy selectors. TODO: use className?
|
||||
...(isTabbable
|
||||
? createAriaClickAttrs(handleInteraction)
|
||||
: { onClick: handleInteraction }
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return { 'aria-label': dateStr }
|
||||
}
|
17
fullcalendar-main/packages/core/src/common/render-hook.tsx
Normal file
17
fullcalendar-main/packages/core/src/common/render-hook.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
/* eslint max-classes-per-file: off */
|
||||
|
||||
import { ComponentChildren } from '../preact.js'
|
||||
import { ClassNamesInput } from '../util/html.js'
|
||||
|
||||
export type MountArg<ContentArg> = ContentArg & { el: HTMLElement }
|
||||
export type DidMountHandler<TheMountArg extends { el: HTMLElement }> = (mountArg: TheMountArg) => void
|
||||
export type WillUnmountHandler<TheMountArg extends { el: HTMLElement }> = (mountArg: TheMountArg) => void
|
||||
|
||||
export interface ObjCustomContent {
|
||||
html: string
|
||||
domNodes: any[]
|
||||
}
|
||||
|
||||
export type CustomContent = ComponentChildren | ObjCustomContent
|
||||
export type CustomContentGenerator<RenderProps> = CustomContent | ((renderProps: RenderProps, createElement: any) => (CustomContent | true))
|
||||
export type ClassNamesGenerator<RenderProps> = ClassNamesInput | ((renderProps: RenderProps) => ClassNamesInput)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue