Full Code of InfectoOne/vue-ganttastic for AI

master b06e161fba42 cached
49 files
98.2 KB
29.3k tokens
24 symbols
1 requests
Download .txt
Repository: InfectoOne/vue-ganttastic
Branch: master
Commit: b06e161fba42
Files: 49
Total size: 98.2 KB

Directory structure:
gitextract_6_i3sk9e/

├── .browserslistrc
├── .editorconfig
├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── deploy.yml
├── .gitignore
├── .prettierrc
├── README.md
├── deploy.sh
├── docs/
│   ├── .vitepress/
│   │   ├── config.ts
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.js
│   ├── GGanttChart.md
│   ├── GGanttRow.md
│   ├── common-use-cases.md
│   ├── examples.md
│   ├── getting-started.md
│   ├── index.md
│   └── introduction.md
├── docs-deploy.yml
├── env.d.ts
├── index.html
├── package.json
├── src/
│   ├── GanttPlayground.vue
│   ├── color-schemes.ts
│   ├── components/
│   │   ├── GGanttBar.vue
│   │   ├── GGanttBarTooltip.vue
│   │   ├── GGanttChart.vue
│   │   ├── GGanttCurrentTime.vue
│   │   ├── GGanttGrid.vue
│   │   ├── GGanttLabelColumn.vue
│   │   ├── GGanttRow.vue
│   │   └── GGanttTimeaxis.vue
│   ├── composables/
│   │   ├── createBarDrag.ts
│   │   ├── useBarDragLimit.ts
│   │   ├── useBarDragManagement.ts
│   │   ├── useDayjsHelper.ts
│   │   ├── useTimePositionMapping.ts
│   │   └── useTimeaxisUnits.ts
│   ├── playground.ts
│   ├── provider/
│   │   ├── provideConfig.ts
│   │   ├── provideEmitBarEvent.ts
│   │   ├── provideGetChartRows.ts
│   │   └── symbols.ts
│   ├── types.ts
│   └── vue-ganttastic.ts
├── tsconfig.config.json
├── tsconfig.json
└── vite.config.mts

================================================
FILE CONTENTS
================================================

================================================
FILE: .browserslistrc
================================================
> 1%
last 2 versions
not dead


================================================
FILE: .editorconfig
================================================
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100

================================================
FILE: .eslintrc.cjs
================================================
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")

module.exports = {
  root: true,

  extends: [
    "plugin:vue/vue3-recommended",
    "eslint:recommended",
    "@vue/eslint-config-typescript/recommended",
    "@vue/eslint-config-prettier"
  ],

  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "@typescript-eslint/no-unused-vars": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    quotes: ["error", "double"],
    // "object-curly-spacing": ["error", "never"],
    "prettier/prettier": ["error", {}, { usePrettierrc: true }]
  }
}


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry


================================================
FILE: .github/workflows/deploy.yml
================================================
# Sample workflow for building and deploying a VitePress site to GitHub Pages
#
name: Deploy VitePress site to Pages

on:
  # Runs on pushes targeting the `main` branch. Change this to `master` if you're
  # using the `master` branch as the default branch.
  push:
    branches: [master]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Not needed if lastUpdated is not enabled
      # - uses: pnpm/action-setup@v3 # Uncomment this if you're using pnpm
      # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm # or pnpm / yarn
      - name: Setup Pages
        uses: actions/configure-pages@v4
      - name: Install dependencies
        run: npm ci # or pnpm install / yarn install / bun install
      - name: Build with VitePress
        run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: docs/.vitepress/dist

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/lib
/lib_types


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Docs dist directory
docs/.vitepress/cache
docs/.vitepress/temp
docs/.vitepress/dist

*.tgz


================================================
FILE: .prettierrc
================================================
{
  "semi": false,
  "trailingComma": "none",
  "endOfLine": "auto"
}

================================================
FILE: README.md
================================================
# Vue Ganttastic

<div style="display: flex; flex-direction: column; align-items:center;">
<img
    src="https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png" 
    style="margin: 10px;" height="150"
    alt="Vue Ganttastic logo"
/>

<b>Vue Ganttastic</b> is a simple, interactive and highly customizable Gantt chart component for Vue 3.

![image](https://user-images.githubusercontent.com/28678851/148191571-76bd8d61-4583-4538-8c59-cc2915494890.png)

</div>

## Features

- **[Vue 3](https://v3.vuejs.org/) support**
- **[TypeScript](https://www.typescriptlang.org/) support** _(ships with out of the box type declarations)_
- **Interactivity** _(dynamic, movable and pushable bars)_
- **Reactivity / Responsiveness** (_when changes occur, bars are repositioned accordingly_)
- **Customization options** (_chart/bar styling, slots, event handlers etc._)

Using Vue 2? Check out [Vue-Ganttastic v1](https://github.com/zunnzunn/vue-ganttastic/tree/vue-ganttastic-v1).

## Guide and Docs

For further guides and references, check out the [official docs](https://zunnzunn.github.io/vue-ganttastic/getting-started.html).

## Quickstart

Install using

```
npm install @infectoone/vue-ganttastic
```

Then, initalize the plugin in the starting point of your app (most likely src/main.js):

```js
import { createApp } from "vue"
import App from "./App.vue"
...
import ganttastic from '@infectoone/vue-ganttastic'
...
createApp(App)
  .use(ganttastic)
  .mount('#app')
```

This will globally register the components g-gantt-chart and g-gantt-row and you will be able to use those two components right away.

```html
<template>
  <g-gantt-chart
    chart-start="2021-07-12 12:00"
    chart-end="2021-07-14 12:00"
    precision="hour"
    bar-start="myBeginDate"
    bar-end="myEndDate"
  >
    <g-gantt-row label="My row 1" :bars="row1BarList" />
    <g-gantt-row label="My row 2" :bars="row2BarList" />
  </g-gantt-chart>
</template>

<script setup>
  import { ref } from "vue"

  const row1BarList = ref([
    {
      myBeginDate: "2021-07-13 13:00",
      myEndDate: "2021-07-13 19:00",
      ganttBarConfig: {
        // each bar must have a nested ganttBarConfig object ...
        id: "unique-id-1", // ... and a unique "id" property
        label: "Lorem ipsum dolor"
      }
    }
  ])
  const row2BarList = ref([
    {
      myBeginDate: "2021-07-13 00:00",
      myEndDate: "2021-07-14 02:00",
      ganttBarConfig: {
        id: "another-unique-id-2",
        hasHandles: true,
        label: "Hey, look at me",
        style: {
          // arbitrary CSS styling for your bar
          background: "#e09b69",
          borderRadius: "20px",
          color: "black"
        },
        class: "foo" // you can also add CSS classes to your bars!
      }
    }
  ])
</script>
```

## Contributing

Clone the project, make some changes, test your changes out, create a pull request with a short summary of what changes you made. Contributing is warmly welcomed!

To test your changes out before creating a pull request, create a build:

```
npm run build
```

To test out the build, you should create a tarball using:

```
npm pack
```

Then, place the tarball in some other test project and install the package from the tarball by using:

```
npm install <name_of_the_package>.tgz
```


## About

**License** [MIT](https://choosealicense.com/licenses/mit/)  
**Author**: Marko Žunić, BSc  
[GitHub Repository](https://github.com/zunnzunn/vue-ganttastic)

## Support the project!

In case you found the library useful, a little tip would be much appreciated!

<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="hosted_button_id" value="M63C8DAMV5YDJ" />
<input type="image" src="https://pics.paypal.com/00/s/MTdhMWZmNTUtOWQ1Yi00YmRjLWJjMjgtY2Y0NTNhODM0OTJl/file.PNG" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" style="max-width:200px"/>
<img alt="" border="0" src="https://www.paypal.com/en_AT/i/scr/pixel.gif" width="1" height="1" />
</form>

BTC address:  
![image](https://user-images.githubusercontent.com/28678851/233090745-a0a6d8a4-6df6-4b82-ac0c-90e69551786e.png)

## Screenshots

![image](https://user-images.githubusercontent.com/28678851/148191571-76bd8d61-4583-4538-8c59-cc2915494890.png)

![image](https://user-images.githubusercontent.com/28678851/148191529-b50c0d17-bcc1-4a78-9d2c-ff2a36b03f52.png)

![image](https://user-images.githubusercontent.com/28678851/148191757-a2520dce-aeed-43df-87b2-3a64e225f9e7.png)


================================================
FILE: deploy.sh
================================================
set -e

npm run docs:build
cd docs/.vuepress/dist

git init
git add -A
git commit -m 'deploy'

git push -f git@github.com:InfectoOne/vue-ganttastic.git master:gh-pages

cd -

================================================
FILE: docs/.vitepress/config.ts
================================================
import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
  lang: 'en-US',
  title: 'Vue-Ganttastic',
  description: 'Simple and customizable Gantt chart component for Vue 3.',
  base: '/vue-ganttastic/',
  head: [['link', { rel: 'icon', href: 'https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png' }]],
  themeConfig: {
    logo: 'https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png',
    nav: [
      { text: 'Home', link: '/' },
    ],
    sidebar: [
      { text: 'Introduction', link: '/introduction'},
      { text: 'Getting Started',link: '/getting-started' },
      { text: 'Common use cases', link: '/common-use-cases' },
      { text: 'Examples', link: '/examples' },
      {
        text: 'API Reference',
        items: [
          { text: 'GGanttChart', link: '/GGanttChart' },
          { text: 'GGanttRow', link: '/GGanttRow' }
        ]
      }
    ],
    socialLinks: [
      { icon: 'github', link: 'https://github.com/zunnzunn/vue-ganttastic' }
    ]
  }
})


================================================
FILE: docs/.vitepress/theme/custom.css
================================================
:root {
    --vp-home-hero-name-color: #2fb585;
    --vp-button-brand-bg: #2fb585;
    --vp-button-brand-hover-bg: #354b5d;
    --vp-c-brand: #2fb585;
}

================================================
FILE: docs/.vitepress/theme/index.js
================================================
import DefaultTheme from 'vitepress/theme'
import './custom.css'
import {ganttastic} from "../../../src/vue-ganttastic"

export default {
    extends: DefaultTheme,
    enhanceApp(ctx) {
        ctx.app.use(ganttastic)

    }
}

================================================
FILE: docs/GGanttChart.md
================================================
# API: GGanttChart
The main component of Vue Ganttastic. Represents an entire chart and is meant to have at least one `g-gantt-row` child component.
## Props
| Prop        | Type    | Default | Description                  |
|-------------|---------|---------|------------------------------|
| `chart-start` | string | | Start date-time of the chart.
| `chart-end` | string  | | End date-time of the chart.
| `precision` | string? | `"hour"` | Display precision of the time-axis. Possible values: `hour`, `day`, `date`, `week` and `month`. |
| `bar-start` | string | | Name of the property in bar objects that represents the start date.
| `bar-end` | string  | | Name of the property in bar objects that represents the end date .
| `date-format` | string \| false  | `"YYYY-MM-DD HH:mm"` | Datetime string format of `chart-start`, `chart-end` and the values of the `bar-start`, `bar-end` properties in bar objects. See [Day.js format tokens](https://day.js.org/docs/en/parse/string-format). If the aforementioned properties are native JavaScript [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) objects in your use case, pass `false`.
| `width` | string? | `"100%"` | Width of the chart (e.g. `80%` or `800px`)
| `hide-timeaxis` | boolean? | `false` | Toggle visibility of the time axis.
| `color-scheme` | string \| ColorScheme | `"default"` | Color scheme (theme) of the chart. Either use the name of one of the predefined schemes or pass a color-scheme-object of your own. See [color schemes](#color-schemes).
| `grid` | string? | `false` | Toggle visibility of background grid.
| `current-time` | boolean? | `false` | Toggle visibility of current time marker.
| `current-time-label` | string? | `''` | Text to be displayed next to the current time marker.
| `push-on-overlap` | boolean? | `false` | Specifies whether bars "push one another" when dragging and overlaping.
| `no-overlap` | boolean? |  `false` | If `push-on-overlap` is `false`, toggle this to prevent overlaps after drag by snapping the dragged bar back to its original position.
| `row-height` | number? | `40` | Height of each row in pixels.
| `highlighted-units` | number[]? | `[]` | The time units specified here will be visually highlighted in the chart with a mild opaque color.
| `font` | string | `"Helvetica"`| Font family of the chart.
| `label-column-title` | string? | `''` | If specified, a dedicated column for the row labels will be rendered on the left side of the graph. The specified title is displayed in the upper left corner, as the column's header.
| `label-column-width` | string? | `150px` | Width of the column containing the row labels (if `label-column-title` specified)


## Custom Events
| Event name                 | Event data                                                 |
|----------------------------|------------------------------------------------------------|
| `click-bar`                |  `{bar: GanttBarObject, e: MouseEvent, datetime?: string}` |
| `mousedown-bar`            |  `{bar: GanttBarObject, e: MouseEvent, datetime?: string}` |
| `mouseup-bar`            |  `{bar: GanttBarObject, e: MouseEvent, datetime?: string}` |
| `dblclick-bar`            |  `{bar: GanttBarObject, e: MouseEvent, datetime?: string}` |
| `mouseenter-bar`            |  `{bar: GanttBarObject, e: MouseEvent}` |
| `mouseleave-bar`            |  `{bar: GanttBarObject, e: MouseEvent}` |
| `dragstart-bar`            |  `{bar: GanttBarObject, e: MouseEvent}` |
| `drag-bar`            |  `{bar: GanttBarObject, e: MouseEvent}` |
| `dragend-bar`            |  `{bar: GanttBarObject, e: MouseEvent, movedBars?: Map<GanttBarObject, {oldStart: string, oldEnd: string}>}` |
| `contextmenu-bar`            |  `{bar: GanttBarObject, e: MouseEvent, datetime?: string}` |


## Slots
| Slot name                  | Slot data             | Description                             |
|----------------------------|-----------------------| ----------------------------------------|
| `upper-timeunit`           |  `{label: string, value: string}` | Content of an upper time-unit section in the time axis. |
| `timeunit`           |  `{label: string, value: string}` | Content of a time-unit section in the time axis. |
| `bar-tooltip`        |  `{bar: GanttBarObject}` | Slot for the tooltip which appears when hovering over a bar. |
| `current-time-label`        |  | Slot for the text shown next to the current time marker when the prop `current-time` is set to `true`. |
| `label-column-title`        |  | Slot for the title of the extra column to the left where the row labels are shown if the prop `label-column-title` is set. |
| `label-column-row`       | `{ label: string } ` | Slot for the label of a row if `label-column-title` is set.  |

## Color Schemes  

List of pre-defined color schemes:
- `default`
- `creamy`
- `crimson`
- `dark`
- `flare`
- `fuchsia`
- `grove`
- `material-blue`
- `sky`
- `slumber`
- `vue`

You can also provide your own color scheme. Your custom color scheme should be an object of the following shape:
```typescript
{
  primary: string,
  secondary: string,
  ternary: string,
  quartenary: string,
  hoverHighlight: string,
  markerCurrentTime: string,
  text: string,
  background: string,
  toast?: string
}
```

================================================
FILE: docs/GGanttRow.md
================================================
# API: GGanttRow
Represents a single row of the chart. It is meant to be a child component of `g-gantt-chart`.  

## Props
| Prop        | Type    | Default | Description                  |
|-------------|---------|---------|------------------------------|
| `label`     |`string?`| `""`    | Text that is floating in the upper left corner of the row.
| `bars`      |`GanttBarObject[]`|  | Array of objects, each representing a bar in this row. Any JavaScript/TypeScript object with a nested `ganttBarConfig` object with a unique `id` string property is compatible. The objects must also contain two properties which represent the start and end datetime of the bar. The names of those properties must be passed  to the `bar-start` and `bar-end` props of the `g-gantt-chart` parent.
| `highlight-on-hover` | `boolean?` | `false` | Used for toggling a background color transition effect on mouse hover.
  
## Custom Events
| Event name                 | Event data                                                 |
|----------------------------|------------------------------------------------------------|
| `drop`                     | `{ e: MouseEvent, datetime: string}`                       |


## Slots
| Slot name                  | Slot data             | Description                             |
|----------------------------|-----------------------| ----------------------------------------|
| `label`           |   | Used for modifying the text that is floating in the upper left corner of the row. |
| `bar-label`        |  `{bar: GanttBarObject}` | Used for modifying the text in a bar. |

================================================
FILE: docs/common-use-cases.md
================================================
# Common use cases
 The following section provides a non-exhausting list of common use cases and special features of Vue Ganttastic with corresponding code snippets.

## Adding new bars
For each row of the chart, you will render a `g-gantt-row` component, which accepts a `bars` prop, which is an array of bar objects. Since the prop is reactive, all you need to do to add a new bar is to push a new bar-object into that array. Just make sure that the new bar-object has a nested `ganttBarConfig` object with a unique `id`, and don't forget to specify the property values for the start and end date of the bar (the properties' names must be the ones you passed to the `bar-start` and `bar-end` props of `g-gantt-chart`):
```vue 
<template>
  <g-gantt-chart
    chart-start="2021-07-11 12:00"
    chart-end="2021-07-15 12:00"
    precision="hour"
    width="100%"
    bar-start="myBeginDate"
    bar-end="myEndDate"
  >
    <g-gantt-row
      label="My row 1"
      :bars="myBarList"
    />
  </g-gantt-chart>
</template>

<script setup>

import { ref } from "vue"

const myBarList = ref([])
const addNewBar = () => {
  const bar = {
    myBeginDate: "2021-07-11 17:00",
    myEndDate: "2021-07-12 03:00",
    ganttBarConfig : {
      id: "some-id-blabla" // make sure this is unique!
    }
  }
  myBarList.push(bar)
}
</script>

```
## Configuring and styling bars
Your bar objects can be of any type and contain any properties you want. The only requirements are:
- a datetime-string property for the bar start date
- a datetime-string property for the bar end date
- a nested object `ganttBarConfig` with a unique string property `id`

For further configuration, you can add some optional properties to the nested `ganttBarConfig` object:

| Property name | Type    | Description           |
|---------------|---------|-----------------------|
| `id`          | `string` | A unique string identifier for the bar.  (**mandatory**) 
| `label`       | `string?`  | Text displayed on the bar.
| `html`        | `string?`  | Optional HTML Code that will be rendered after the label (e.g. for tags). Please sanitize user input to avoid cross site scripting, if applicable.
| `hasHandles`  | `boolean?`  | Used to toggle handles on the left and right side of the bar that can be dragged to change the width of the bar. |
| `immobile`    | `boolean?`  | Used to toggle whether bar can be moved (dragged).
| `bundle`      | `string?`  | A string identifier for a bundle. A bundle is a collection of bars that are dragged simultaneously.
| `style`       | `CSSStyleSheet?`  | CSS-based styling for your bar (e.g `background`, `fontSize`, `borderRadius` etc.).

## Extending the width of a bar
Simply add `hasHandles: true` to the `ganttBarConfig` to make the bar extendable by dragging the handles on its left or right side.  

## Push bars when dragging
By default, bars can overlap with other bars while being dragged. If you would like to prevent this and have the bars "push" one another while dragging, use the `push-on-overlap` prop:
```vue 
  <g-gantt-chart
    ...
    push-on-overlap
    ...
  >
    ...
  </g-gantt-chart>
```

## Bundling bars together
If you want to bind a group of bars one to another so that when you drag one bar, all the others move together with it, specify a `bundle` string in the `ganttBarConfig` of each affected bar.

## Custom behavior on clicking/dragging a bar
It is completely up to you to specify which kind of behavior you would like e.g. when a bar is clicked on or when a bar-drag is ended. For this, you may use special events emitted by `g-gantt-chart`:
```vue
<g-gantt-chart
  ...
  @mousedown-bar="onMousedownBar($event.bar, $event.e, $event.datetime)"
  @dblclick-bar="onMouseupBar($event.bar, $event.e, $event.datetime)"
  @mouseenter-bar="onMouseenterBar($event.bar, $event.e)"
  @mouseleave-bar="onMouseleaveBar($event.bar, $event.e)"
  @dragstart-bar="onDragstartBar($event.bar, $event.e)"
  @drag-bar="onDragBar($event.bar, $event.e)"
  @dragend-bar="onDragendBar($event.bar, $event.e, $event.movedBars)"
  @contextmenu-bar="onContextmenuBar($event.bar, $event.e, $event.datetime)"
>
  ...
</g-gantt-chart>

<script setup lang="ts">
...
const onMousedownBar = (bar: GanttBarObject, e: MouseEvent, datetime?: string) => {
  // do something
}
...
</script>
```
## Drag and drop  
The `g-gantt-row` component comes with a special `drop` event, that you can use to implement custom drag-and-drop behavior. The event data also includes the `datetime` position on which the drop occured.
```vue
<g-gantt-chart
   ...
>
  <g-gantt-row
    label="This is my row"
    :bars="bars1"
    @drop="onDrop($event.e, $event.datetime)"
  />
</g-gantt-chart>

<script setup lang="ts">
...
const onDrop = (e: MouseEvent, datetime?: string) => {
  // do something
}
...
</script>
```

## Time axis precision
If the time-range (`chart-start` to `chart-end`) of your chart is very large, the displayed time units in the time axis might be too dense if the chart is not wide enough. You might want to specify the precision of the time axis accordingly. Use the `precision` prop of `g-gantt-chart` for this. Possible values are `hour`, `day`, `week` and `month`.

## Chart themes
Vue Ganttastic ships with several pre-made color schemes that you may specify using the `color-scheme` prop of `g-gantt-chart`. [List of available color-schemes](https://infectoone.github.io/vue-ganttastic/GGanttChart.html#color-schemes)

## Locale
Since Vue Ganttastic uses Day.js for all datetime manipulations, you can change the locale of Vue Ganttastic by [changing the global locale of Day.js](https://day.js.org/docs/en/i18n/changing-locale). You will usually do this in your `src/main.js` before you initialize the Ganttastic plugin.



================================================
FILE: docs/examples.md
================================================

# Live Demos

## Simple hour chart  
- `precision`: `hour`
<g-gantt-chart chart-start="01.01.2022 12:00" chart-end="02.01.2022 12:00" precision="hour" grid width="100%" bar-start="beginDate" bar-end="endDate" date-format="DD.MM.YYYY HH:mm">
<g-gantt-row label="My row 1" :bars="hourBarList1" highlight-on-hover/>
<g-gantt-row label="Another row" :bars="hourBarList2" highlight-on-hover/>
</g-gantt-chart>

<button @click="addHourBar()" :disabled="hourBarList2.length > 0"> Add bar </button>
<button @click="deleteHourBar()" style="margin-left: 10px" :disabled="hourBarList2.length === 0"> Delete bar </button>



## Day chart with dark theme  
- `precision`: `day`
- `row-height` : `70`
- `no-overlap`
- `color-scheme` : `dark`

Used slots:
`g-gantt-row` >  `label`, `bar-label`
<g-gantt-chart chart-start="30.10.2022 12:00" chart-end="02.11.2022 13:00" precision="day" grid width="100%" :row-height="70" bar-start="beginDate" bar-end="endDate" date-format="DD.MM.YYYY HH:mm" color-scheme="dark" no-overlap>
<g-gantt-row label="Row label" :bars="dayBarList1" highlight-on-hover>
<template #bar-label="{bar}">
<img v-if="bar.imgSrc" :src="bar.imgSrc" height="30" width="30"/>
{{bar.ganttBarConfig.label}}
</template>
</g-gantt-row>
<g-gantt-row label="My row 2" :bars="[]" highlight-on-hover>
<template #label>
<img src='https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png' height="30" width="30" style="padding-right:10px"/>
Label with image
</template>
</g-gantt-row>
</g-gantt-chart>



## Month chart pushing and bundles
- `precision`: `month`
- `push-on-overlap`
- `color-scheme` : `vue`
- `font` : `Courier`
<g-gantt-chart chart-start="01.01.2022 12:00" chart-end="15.03.2022 03:00" precision="month" grid width="100%" bar-start="beginDate" bar-end="endDate" date-format="DD.MM.YYYY HH:mm" color-scheme="vue" font="Courier" push-on-overlap>
<g-gantt-row label="My row 1" :bars="monthBarList1" highlight-on-hover/>
<g-gantt-row label="My row 2" :bars="monthBarList2" highlight-on-hover/>
<g-gantt-row label="Look at me!" :bars="monthBarList3" highlight-on-hover/>
<g-gantt-row label="Fourth row" :bars="[]" highlight-on-hover/>
</g-gantt-chart>



<script setup>
import { ref } from "vue"

const hourBarList1 = ref([
  {
    beginDate: "01.01.2022 15:00",
    endDate: "01.01.2022 19:45",
    ganttBarConfig: {
      id: "8621987329",
      label: "Drag me",
      style: {
        color: "white"
      }
    }
  },
  {
    beginDate: "01.01.2022 23:00",
    endDate: "02.01.2022 08:05",
    ganttBarConfig: {
      id: "8621987322",
      label: "Drag my handles",
      hasHandles: true,
      style: {
        background: "#d66f2a",
        color: "white"
      }
    }
  }
])

const hourBarList2 = ref([])

const dayBarList1 = ref([
  {
    beginDate: "31.10.2022 15:00",
    endDate: "01.11.2022 05:45",
    ganttBarConfig: {
      id: "a621987323",
      label: "Drag me",
      style: {
        background: "#cc2a2d",
        color: "white"
      }
    }
  },
  {
    beginDate: "01.11.2022 09:00",
    endDate: "02.11.2022 08:00",
    imgSrc: "https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png",
    ganttBarConfig: {
      id: "x21987322",
      label: "I have an image",
      hasHandles: true,
      style: {
        background: "#e2e595",
        color: "black",
        borderRadius: "40px"
      }
    }
  }
])

const monthBarList1 = ref([
  {
    beginDate: "01.01.2022 23:00",
    endDate: "02.02.2022 08:05",
    ganttBarConfig: {
      id: "5621987352",
      label: "I'm in a bundle",
      hasHandles: true,
      bundle: "myBundle",
      style: {
        background: "#1c8745",
        color: "white",
        borderRadius: "20px"
      }
    }
  }
])

const monthBarList2 = ref([
  {
    beginDate: "01.01.2022 23:00",
    endDate: "02.02.2022 08:05",
    ganttBarConfig: {
      id: "8621987321",
      label: "I'm in a bundle",
      hasHandles: true,
      bundle: "myBundle",
      style: {
        background: "#a02353",
        color: "white",
        borderRadius: "20px"
      }
    }
  },
  {
    beginDate: "15.02.2022 00:00",
    endDate: "01.03.2022 00:05",
    ganttBarConfig: {
      id: "7721987321",
      label: "Lorem ispum dolor",
      bundle: "bundle2",
      style: {
        backgroundImage: "repeating-linear-gradient(45deg, #ccc, #ccc 30px, #8221b2 30px, #8221b2 60px)",
        borderRadius: "20px",
        color: "black"
      }
    }
  }
])
const monthBarList3 = ref([{
    beginDate: "15.02.2022 00:00",
    endDate: "01.03.2022 00:05",
    ganttBarConfig: {
      id: "7721987325",
      label: "Lorem ispum dolor",
      bundle: "bundle2",
      style: {
        backgroundImage: "repeating-linear-gradient(45deg, #ccc, #ccc 30px, #8221b2 30px, #8221b2 60px)",
        borderRadius: "20px",
        color: "black"
      }
    }
  }])

const addHourBar = () => {
  if (hourBarList2.value.some(bar => bar.ganttBarConfig.id === "test1")) {
    return
  }
  const bar = {
    beginDate: "01.01.2022 18:00",
    endDate: "02.01.2022 02:00",
    ganttBarConfig: {
      id: "test1",
      hasHandles: true,
      label: "Hello!",
      style: {
        background: "#5484b7",
        borderRadius: "20px",
        color: "white"
      }
    }
  }
  hourBarList2.value.push(bar)
}

const deleteHourBar = () => {
  const idx = hourBarList2.value.findIndex(b => b.ganttBarConfig.id === "test1")
  if (idx !== -1) {
    hourBarList2.value.splice(idx, 1)
  }
}
</script>

<style scoped>
  button {
    padding: 10px;
    background: #258A5D;
    color: white;
    border: none;
    border-radius: 5px;
  }
  button:disabled {
    opacity: 0.5;
  }
</style>


================================================
FILE: docs/getting-started.md
================================================
# Getting started

## Install

You can add Vue Ganttastic to your project using <kbd>npm</kbd>:

```
npm install @infectoone/vue-ganttastic
```

Then, initalize the plugin in the starting point of your app (most likely `src/main.js`):

```javascript
import { createApp } from "vue"
import App from "./App.vue"
...
import ganttastic from '@infectoone/vue-ganttastic'
...
createApp(App)
  .use(ganttastic)
  .mount('#app')
```

This will globally register the components `g-gantt-chart` and `g-gantt-row` and you will be able to use those two components right away.

## Basic usage

```vue
<template>
  <g-gantt-chart
    chart-start="2021-07-12 12:00"
    chart-end="2021-07-14 12:00"
    precision="hour"
    bar-start="myBeginDate"
    bar-end="myEndDate"
  >
    <g-gantt-row label="My row 1" :bars="row1BarList" />
    <g-gantt-row label="My row 2" :bars="row2BarList" />
  </g-gantt-chart>
</template>

<script setup>
import { ref } from "vue"

const row1BarList = ref([
  {
    myBeginDate: "2021-07-13 13:00",
    myEndDate: "2021-07-13 19:00",
    ganttBarConfig: {
      // each bar must have a nested ganttBarConfig object ...
      id: "unique-id-1", // ... and a unique "id" property
      label: "Lorem ipsum dolor"
    }
  }
])
const row2BarList = ref([
  {
    myBeginDate: "2021-07-13 00:00",
    myEndDate: "2021-07-14 02:00",
    ganttBarConfig: {
      id: "another-unique-id-2",
      hasHandles: true,
      label: "Hey, look at me",
      style: {
        // arbitrary CSS styling for your bar
        background: "#e09b69",
        borderRadius: "20px",
        color: "black"
      }
    }
  }
])
</script>
```

The result shoud look like this:  
<g-gantt-chart chart-start="2021-07-12 12:00" chart-end="2021-07-14 12:00" precision="hour" width="100%" bar-start="myBeginDate" bar-end="myEndDate"> <g-gantt-row label="My row 1" :bars="row1BarList"/>
<g-gantt-row label="My row 2" :bars="row2BarList"/>
</g-gantt-chart>

<script setup>

import { ref } from "vue"

const row1BarList = ref([
  {
    myBeginDate: "2021-07-13 13:00",
    myEndDate: "2021-07-13 19:00",
    ganttBarConfig: {    // each bar must have a nested ganttBarConfig object ...
      id: "unique-id-1", // ... and a unique "id" property
      label: "Lorem ipsum dolor"
    }
  }
])
const row2BarList = ref([
  {
    myBeginDate: "2021-07-13 00:00",
    myEndDate: "2021-07-14 02:00",
    ganttBarConfig: {
      id: "another-unique-id-2",
      hasHandles: true,
      label: "Hey, look at me",
      style: {     
        // arbitrary CSS styling for your bar
        background: "#e09b69",
        borderRadius: "20px",
        color: "#000000"
      },
      class: "foo" // you can also add CSS classes to your bars!
    }
  }
])
</script>


================================================
FILE: docs/index.md
================================================
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home

hero:
  name: Vue-Ganttastic
  text: Gantt chart component for Vue
  tagline: A simple, interactive and highly customizable Gantt chart component for Vue.js 
  image:
    src: https://user-images.githubusercontent.com/28678851/148047714-301f07df-4101-48b8-9e47-1f272b290e80.png
    alt: Vue-Ganttastic logo
  actions:
    - theme: brand
      text: Get started
      link: /introduction
    - theme: alt
      text: API Reference
      link: /GGanttChart

features:
  - title: Vue 3 and TypeScript support
    details: Written in Vue 3 and TypeScript. Ships with out-of-the-box type declarations.
  - title: Interactive
    details: Dynamic Gantt chart with movable bars and numerous event handlers.
  - title:  Customizable
    details: Style the chart and each individual bar to your own liking!
---



================================================
FILE: docs/introduction.md
================================================
# Introduction
Vue Ganttastic is a simple, interactive and highly customizable Gantt chart component for Vue 3.   

## Features
- **[Vue 3](https://v3.vuejs.org/) support**
- **[TypeScript](https://www.typescriptlang.org/) support** *(ships with out of the box type declarations)*
- **Interactivity** *(dynamic, movable and pushable bars)*
- **Reactivity / Responsiveness** (*when changes occur, bars are repositioned accordingly*)
- **Customization options** (*chart/bar styling, slots, event handlers etc.*)

## About
**License** [MIT](https://choosealicense.com/licenses/mit/)  
**Author**: Marko Žunić, BSc  
[GitHub Repository](https://github.com/InfectoOne/vue-ganttastic)

## Support the project!
In case you found the library useful, a little tip would be much appreciated!

<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="hosted_button_id" value="M63C8DAMV5YDJ" />
<input type="image" src="https://pics.paypal.com/00/s/MTdhMWZmNTUtOWQ1Yi00YmRjLWJjMjgtY2Y0NTNhODM0OTJl/file.PNG" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" style="max-width:200px"/>
<img alt="" border="0" src="https://www.paypal.com/en_AT/i/scr/pixel.gif" width="1" height="1" />
</form>

BTC address:   
![image](https://user-images.githubusercontent.com/28678851/233090745-a0a6d8a4-6df6-4b82-ac0c-90e69551786e.png)  




================================================
FILE: docs-deploy.yml
================================================
# Sample workflow for building and deploying a VitePress site to GitHub Pages
#
name: Deploy VitePress site to Pages

on:
  # Runs on pushes targeting the `main` branch. Change this to `master` if you're
  # using the `master` branch as the default branch.
  push:
    branches: [master]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Not needed if lastUpdated is not enabled
      # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: npm # or pnpm / yarn
      - name: Setup Pages
        uses: actions/configure-pages@v3
      - name: Install dependencies
        run: npm ci # or pnpm install / yarn install
      - name: Build with VitePress
        run: npm run docs:build # or pnpm docs:build / yarn docs:build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: docs/.vitepress/dist

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

================================================
FILE: env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width,initial-scale=1.0">
	<title>@infectoone/vue-ganttastic</title>

	<link rel="icon" href="/favicon.ico">
	<style>
		body {
			font-family: Helvetica, sans-serif;
		}
	</style>
</head>
<body>
<noscript>
	<strong>We're sorry but the app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/playground.ts"></script>
</body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "@infectoone/vue-ganttastic",
  "version": "2.3.2",
  "description": "A simple and customizable Gantt chart component for Vue.js",
  "author": "Marko Zunic (@zunnzunn)",
  "scripts": {
    "serve": "vite",
    "build": "npm run build:types && npm run build:lib",
    "build:lib": "vite build",
    "build:types": "vue-tsc --declaration --emitDeclarationOnly --outDir lib_types",
    "typecheck": "vue-tsc --noEmit",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
    "lint:fix": "npm run lint --fix",
    "docs:build": "vitepress build docs",
    "docs:dev": "vitepress dev docs",
    "docs:preview": "vitepress preview docs"
  },
  "type": "module",
  "exports": {
    ".": {
      "types": "./lib_types/vue-ganttastic.d.ts",
      "import": "./lib/vue-ganttastic.js",
      "require": "./lib/vue-ganttastic.umd.cjs"
    }
  },
  "main": "./lib/vue-ganttastic.umd.cjs",
  "types": "./lib_types/vue-ganttastic.d.ts",
  "files": [
    "lib_types",
    "lib/vue-ganttastic.js",
    "lib/vue-ganttastic.umd.cjs"
  ],
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.2.0",
    "@senojs/rollup-plugin-style-inject": "^0.1.1",
    "@types/node": "^16.11.58",
    "@types/postcss-preset-env": "^7.7.0",
    "@vitejs/plugin-vue": "^3.1.2",
    "@vue/eslint-config-prettier": "^7.0.0",
    "@vue/eslint-config-typescript": "^11.0.2",
    "@vue/tsconfig": "^0.1.3",
    "@vuepress/plugin-search": "2.0.0-beta.51",
    "eslint": "^8.25.0",
    "eslint-plugin-vue": "^9.6.0",
    "npm-run-all": "^4.1.5",
    "postcss": "^8.4.17",
    "postcss-preset-env": "^7.8.2",
    "prettier": "^2.7.1",
    "typescript": "~4.8.4",
    "vite": "^3.1.6",
    "vitepress": "^1.0.0-rc.4",
    "vue-tsc": "^1.8.27"
  },
  "peerDependencies": {
    "dayjs": "^1.11.5",
    "vue": "^3.2.40"
  },
  "homepage": "https://zunnzunn.github.io/vue-ganttastic/",
  "keywords": [
    "gantt",
    "chart",
    "bar",
    "diagram",
    "vue",
    "vuejs",
    "ganttastic"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/zunnzunn/vue-ganttastic"
  },
  "dependencies": {
    "@vueuse/core": "^9.1.1"
  }
}


================================================
FILE: src/GanttPlayground.vue
================================================
<template>
  <g-gantt-chart
    :chart-start="chartStart"
    :chart-end="chartEnd"
    precision="week"
    :row-height="40"
    grid
    current-time
    width="100%"
    bar-start="beginDate"
    bar-end="endDate"
    :date-format="format"
    @click-bar="onClickBar($event.bar, $event.e, $event.datetime)"
    @mousedown-bar="onMousedownBar($event.bar, $event.e, $event.datetime)"
    @dblclick-bar="onMouseupBar($event.bar, $event.e, $event.datetime)"
    @mouseenter-bar="onMouseenterBar($event.bar, $event.e)"
    @mouseleave-bar="onMouseleaveBar($event.bar, $event.e)"
    @dragstart-bar="onDragstartBar($event.bar, $event.e)"
    @drag-bar="onDragBar($event.bar, $event.e)"
    @dragend-bar="onDragendBar($event.bar, $event.e, $event.movedBars)"
    @contextmenu-bar="onContextmenuBar($event.bar, $event.e, $event.datetime)"
  >
    <g-gantt-row label="My row to test" :bars="bars1" highlight-on-hover />
    <g-gantt-row label="My another new row to test" highlight-on-hover :bars="bars2" />
    <g-gantt-row label="just another row to test gantt" highlight-on-hover :bars="bars3" />
    <g-gantt-row
      label="errors teach us, and debugging makes us stronger!"
      highlight-on-hover
      :bars="bars4"
    />
  </g-gantt-chart>

  <button type="button" @click="addBar()">Add bar</button>
  <button type="button" @click="deleteBar()">Delete bar</button>
</template>

<script setup lang="ts">
import { ref } from "vue"
import type { GanttBarObject } from "./types"
import dayjs from "dayjs"

const format = ref("DD.MM.YYYY HH:mm")
const chartStart = ref(dayjs().startOf("day").format(format.value))
const chartEnd = ref(
  dayjs(chartStart.value, format.value).add(3, "days").hour(12).format(format.value)
)

const bars1 = ref<GanttBarObject[]>([
  {
    beginDate: dayjs().hour(13).startOf("hour").format(format.value),
    endDate: dayjs().hour(19).startOf("hour").format(format.value),
    ganttBarConfig: {
      id: "8621987329",
      label: "I'm in a bundle",
      bundle: "bundle2"
    }
  }
])

const bars2 = ref([
  {
    beginDate: dayjs().hour(13).startOf("hour").format(format.value),
    endDate: dayjs().hour(19).startOf("hour").format(format.value),
    ganttBarConfig: {
      id: "1592311887",
      label: "I'm in a bundle",
      bundle: "bundle2",
      style: {
        background: "magenta"
      }
    }
  },
  {
    beginDate: dayjs().add(2, "day").hour(0).startOf("hour").format(format.value),
    endDate: dayjs().add(2, "day").hour(19).startOf("hour").format(format.value),
    ganttBarConfig: {
      id: "7716981641",
      label: "Lorem ipsum dolor",
      hasHandles: true,
      style: {
        background: "#b74b52"
      }
    }
  },
  {
    beginDate: dayjs().add(1, "day").hour(4).startOf("hour").format(format.value),
    endDate: dayjs().add(1, "day").hour(16).startOf("hour").format(format.value),
    ganttBarConfig: {
      id: "9716981641",
      label: "Oh hey",
      style: {
        background: "#69e064",
        borderRadius: "15px",
        color: "blue",
        fontSize: "10px"
      }
    }
  }
])

const bars3 = [
  {
    beginDate: "15.01.2024 08:30",
    endDate: "20.02.2024 16:45",
    ganttBarConfig: {
      id: "9876543210",
      label: "Updated Bundle",
      bundle: "bundle3",
      style: {
        background: "cyan"
      }
    }
  },
  {
    beginDate: "20.02.2024 12:00",
    endDate: "10.03.2024 18:30",
    ganttBarConfig: {
      id: "1234567890",
      label: "New Task",
      hasHandles: true,
      style: {
        background: "#f79466"
      }
    }
  },
  {
    beginDate: "25.04.2024 09:15",
    endDate: "30.04.2024 21:00",
    ganttBarConfig: {
      id: "2468135790",
      label: "Greetings",
      style: {
        background: "#aabbcc",
        borderRadius: "8px",
        color: "white",
        fontSize: "12px"
      }
    }
  }
]

const bars4 = [
  {
    beginDate: "10.01.2024 08:00",
    endDate: "15.03.2024 16:30",
    ganttBarConfig: {
      id: "9876543210",
      label: "Novo Pacote",
      bundle: "pacote3",
      style: {
        background: "pink"
      }
    }
  },
  {
    beginDate: "05.03.2024 10:00",
    endDate: "15.04.2024 22:15",
    ganttBarConfig: {
      id: "2468135790",
      label: "hello folks",
      style: {
        background: "#ffd700",
        borderRadius: "10px",
        color: "black",
        fontSize: "14px"
      }
    }
  }
]

const addBar = () => {
  if (bars1.value.some((bar) => bar.ganttBarConfig.id === "test1")) {
    return
  }
  const bar = {
    beginDate: dayjs().add(1, "day").hour(4).startOf("hour").format(format.value),
    endDate: dayjs().add(2, "day").hour(4).startOf("hour").format(format.value),
    ganttBarConfig: {
      id: "test1",
      hasHandles: true,
      label: "Hello!",
      style: {
        background: "#5484b7",
        borderRadius: "20px"
      }
    }
  }
  bars1.value.push(bar)
}

const deleteBar = () => {
  const idx = bars1.value.findIndex((b) => b.ganttBarConfig.id === "test1")
  if (idx !== -1) {
    bars1.value.splice(idx, 1)
  }
}

const onClickBar = (bar: GanttBarObject, e: MouseEvent, datetime?: string) => {
  console.log("click-bar", bar, e, datetime)
}

const onMousedownBar = (bar: GanttBarObject, e: MouseEvent, datetime?: string) => {
  console.log("mousedown-bar", bar, e, datetime)
}

const onMouseupBar = (bar: GanttBarObject, e: MouseEvent, datetime?: string) => {
  console.log("mouseup-bar", bar, e, datetime)
}

const onMouseenterBar = (bar: GanttBarObject, e: MouseEvent) => {
  console.log("mouseenter-bar", bar, e)
}

const onMouseleaveBar = (bar: GanttBarObject, e: MouseEvent) => {
  console.log("mouseleave-bar", bar, e)
}

const onDragstartBar = (bar: GanttBarObject, e: MouseEvent) => {
  console.log("dragstart-bar", bar, e)
}

const onDragBar = (bar: GanttBarObject, e: MouseEvent) => {
  console.log("drag-bar", bar, e)
}

const onDragendBar = (
  bar: GanttBarObject,
  e: MouseEvent,
  movedBars?: Map<GanttBarObject, { oldStart: string; oldEnd: string }>
) => {
  console.log("dragend-bar", bar, e, movedBars)
}

const onContextmenuBar = (bar: GanttBarObject, e: MouseEvent, datetime?: string) => {
  console.log("contextmenu-bar", bar, e, datetime)
}
</script>


================================================
FILE: src/color-schemes.ts
================================================
import type * as CSS from "csstype"

type Color = CSS.DataType.Color

export type ColorScheme = {
  primary: Color
  secondary: Color
  ternary: Color
  quartenary: Color
  hoverHighlight: Color
  markerCurrentTime: Color
  text: Color
  background: Color
  toast?: Color
}

export const colorSchemes: Record<string, ColorScheme> = {
  default: {
    primary: "#eeeeee",
    secondary: "#E0E0E0",
    ternary: "#F5F5F5",
    quartenary: "#ededed",
    hoverHighlight: "rgba(204, 216, 219, 0.5)",
    markerCurrentTime: "#000",
    text: "#404040",
    background: "white"
  },

  creamy: {
    primary: "#ffe8d9",
    secondary: "#fcdcc5",
    ternary: "#fff6f0",
    quartenary: "#f7ece6",
    hoverHighlight: "rgba(230, 221, 202, 0.5)",
    markerCurrentTime: "#000",
    text: "#542d05",
    background: "white"
  },

  crimson: {
    primary: "#a82039",
    secondary: "#c41238",
    ternary: "#db4f56",
    quartenary: "#ce5f64",
    hoverHighlight: "rgba(196, 141, 141, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  },

  dark: {
    primary: "#404040",
    secondary: "#303030",
    ternary: "#353535",
    quartenary: "#383838",
    hoverHighlight: "rgba(159, 160, 161, 0.5)",
    markerCurrentTime: "#fff",
    text: "white",
    background: "#525252",
    toast: "#1f1f1f"
  },

  flare: {
    primary: "#e08a38",
    secondary: "#e67912",
    ternary: "#5e5145",
    quartenary: "#665648",
    hoverHighlight: "rgba(196, 141, 141, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  },

  fuchsia: {
    primary: "#de1d5a",
    secondary: "#b50b41",
    ternary: "#ff7da6",
    quartenary: "#f2799f",
    hoverHighlight: "rgba(196, 141, 141, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  },

  grove: {
    primary: "#3d9960",
    secondary: "#288542",
    ternary: "#72b585",
    quartenary: "#65a577",
    hoverHighlight: "rgba(160, 219, 171, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  },

  "material-blue": {
    primary: "#0D47A1",
    secondary: "#1565C0",
    ternary: "#42a5f5",
    quartenary: "#409fed",
    hoverHighlight: "rgba(110, 165, 196, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  },

  sky: {
    primary: "#b5e3ff",
    secondary: "#a1d6f7",
    ternary: "#d6f7ff",
    quartenary: "#d0edf4",
    hoverHighlight: "rgba(193, 202, 214, 0.5)",
    markerCurrentTime: "#000",
    text: "#022c47",
    background: "white"
  },

  slumber: {
    primary: "#2a2f42",
    secondary: "#2f3447",
    ternary: "#35394d",
    quartenary: "#2c3044",
    hoverHighlight: "rgba(179, 162, 127, 0.5)",
    markerCurrentTime: "#fff",
    text: "#ffe0b3",
    background: "#38383b",
    toast: "#1f1f1f"
  },

  vue: {
    primary: "#258a5d",
    secondary: "#41B883",
    ternary: "#35495E",
    quartenary: "#2a3d51",
    hoverHighlight: "rgba(160, 219, 171, 0.5)",
    markerCurrentTime: "#000",
    text: "white",
    background: "white"
  }
}

export type ColorSchemeKey = keyof typeof colorSchemes

export default colorSchemes


================================================
FILE: src/components/GGanttBar.vue
================================================
<template>
  <div
    :id="barConfig.id"
    :class="['g-gantt-bar', barConfig.class || '']"
    :style="{
      ...barConfig.style,
      position: 'absolute',
      top: `${rowHeight * 0.1}px`,
      left: `${xStart}px`,
      width: `${xEnd - xStart}px`,
      height: `${rowHeight * 0.8}px`,
      zIndex: isDragging ? 3 : 2
    }"
    @mousedown="onMouseEvent"
    @click="onMouseEvent"
    @dblclick="onMouseEvent"
    @mouseenter="onMouseEvent"
    @mouseleave="onMouseEvent"
    @contextmenu="onMouseEvent"
  >
    <div class="g-gantt-bar-label">
      <slot :bar="bar">
        <div>
          {{ barConfig.label || "" }}
        </div>
        <div v-if="barConfig.html" v-html="barConfig.html"/>
      </slot>
    </div>
    <template v-if="barConfig.hasHandles">
      <div class="g-gantt-bar-handle-left" />
      <div class="g-gantt-bar-handle-right" />
    </template>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, toRefs, watch, onMounted, inject } from "vue"

import useBarDragManagement from "../composables/useBarDragManagement.js"
import useTimePositionMapping from "../composables/useTimePositionMapping.js"
import useBarDragLimit from "../composables/useBarDragLimit.js"
import type { GanttBarObject } from "../types"
import provideEmitBarEvent from "../provider/provideEmitBarEvent.js"
import provideConfig from "../provider/provideConfig.js"
import { BAR_CONTAINER_KEY } from "../provider/symbols"

const props = defineProps<{
  bar: GanttBarObject
}>()
const emitBarEvent = provideEmitBarEvent()
const config = provideConfig()
const { rowHeight } = config

const { bar } = toRefs(props)
const { mapTimeToPosition, mapPositionToTime } = useTimePositionMapping()
const { initDragOfBar, initDragOfBundle } = useBarDragManagement()
const { setDragLimitsOfGanttBar } = useBarDragLimit()

const isDragging = ref(false)

const barConfig = computed(() => bar.value.ganttBarConfig)

function firstMousemoveCallback(e: MouseEvent) {
  barConfig.value.bundle != null ? initDragOfBundle(bar.value, e) : initDragOfBar(bar.value, e)
  isDragging.value = true
}

const prepareForDrag = () => {
  setDragLimitsOfGanttBar(bar.value)
  if (barConfig.value.immobile) {
    return
  }

  window.addEventListener("mousemove", firstMousemoveCallback, {
    once: true
  }) // on first mousemove event
  window.addEventListener(
    "mouseup",
    () => {
      // in case user does not move the mouse after mousedown at all
      window.removeEventListener("mousemove", firstMousemoveCallback)
      isDragging.value = false
    },
    { once: true }
  )
}

const barContainerEl = inject(BAR_CONTAINER_KEY)

const onMouseEvent = (e: MouseEvent) => {
  e.preventDefault()
  if (e.type === "mousedown") {
    prepareForDrag()
  }
  const barContainer = barContainerEl?.value?.getBoundingClientRect()
  if (!barContainer) {
    return
  }
  const datetime = mapPositionToTime(e.clientX - barContainer.left)
  emitBarEvent(e, bar.value, datetime)
}

const { barStart, barEnd, width, chartStart, chartEnd, chartSize } = config

const xStart = ref(0)
const xEnd = ref(0)

onMounted(() => {
  watch(
    [bar, width, chartStart, chartEnd, chartSize.width],
    () => {
      xStart.value = mapTimeToPosition(bar.value[barStart.value])
      xEnd.value = mapTimeToPosition(bar.value[barEnd.value])
    },
    { deep: true, immediate: true }
  )
})
</script>

<style>
.g-gantt-bar {
  display: flex;
  justify-content: center;
  align-items: center;
  background: cadetblue;
  overflow: hidden;
}

.g-gantt-bar-label {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 0 14px 0 14px; /* 14px is the width of the handle */
  display: flex;
  justify-content: center;
  align-items: center;
}
.g-gantt-bar-label > * {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.g-gantt-bar-handle-left,
.g-gantt-bar-handle-right {
  position: absolute;
  width: 10px;
  height: 100%;
  background: white;
  opacity: 0.7;
  border-radius: 0px;
  cursor: ew-resize;
  top: 0;
}
.g-gantt-bar-handle-left {
  left: 0;
}
.g-gantt-bar-handle-right {
  right: 0;
}

.g-gantt-bar-label img {
  pointer-events: none;
}
</style>


================================================
FILE: src/components/GGanttBarTooltip.vue
================================================
<template>
  <teleport to="body">
    <transition name="g-fade" mode="out-in">
      <div
        v-if="modelValue"
        class="g-gantt-tooltip"
        :style="{
          top: tooltipTop,
          left: tooltipLeft,
          fontFamily: font
        }"
      >
        <div class="g-gantt-tooltip-color-dot" :style="{ background: dotColor }" />
        <slot :bar="bar" :bar-start="barStartRaw" :bar-end="barEndRaw">
          {{ tooltipContent }}
        </slot>
      </div>
    </transition>
  </teleport>
</template>

<script setup lang="ts">
import { computed, toRefs, ref, watch, nextTick } from "vue"

import type { GanttBarObject } from "../types"
import useDayjsHelper from "../composables/useDayjsHelper.js"
import provideConfig from "../provider/provideConfig.js"

const TOOLTIP_FORMATS = {
  hour: "HH:mm",
  day: "DD. MMM HH:mm",
  date: "DD. MMMM YYYY",
  month: "DD. MMMM YYYY",
  week: "DD. MMMM YYYY (WW)"
} as const

const DEFAULT_DOT_COLOR = "cadetblue"

const props = defineProps<{
  bar: GanttBarObject | undefined
  modelValue: boolean
}>()

const { bar } = toRefs(props)
const { precision, font, barStart, barEnd, rowHeight } = provideConfig()

const tooltipTop = ref("0px")
const tooltipLeft = ref("0px")
watch(
  () => props.bar,
  async () => {
    await nextTick()

    const barId = bar?.value?.ganttBarConfig.id || ""
    if (!barId) {
      return
    }

    const barElement = document.getElementById(barId)
    const { top, left } = barElement?.getBoundingClientRect() || {
      top: 0,
      left: 0
    }
    const leftValue = Math.max(left, 10)
    tooltipTop.value = `${top + rowHeight.value - 10}px`
    tooltipLeft.value = `${leftValue}px`
  },
  { deep: true, immediate: true }
)

const dotColor = computed(() => bar?.value?.ganttBarConfig.style?.background || DEFAULT_DOT_COLOR)

const { toDayjs } = useDayjsHelper()

const barStartRaw = computed(() => bar.value?.[barStart.value])
const barEndRaw = computed(() => bar.value?.[barEnd.value])

const tooltipContent = computed(() => {
  if (!bar?.value) {
    return ""
  }
  const format = TOOLTIP_FORMATS[precision.value]
  const barStartFormatted = toDayjs(barStartRaw.value).format(format)
  const barEndFormatted = toDayjs(barEndRaw.value).format(format)
  return `${barStartFormatted} \u2013 ${barEndFormatted}`
})
</script>

<style>
.g-gantt-tooltip {
  position: fixed;
  background: black;
  color: white;
  z-index: 4;
  font-size: 0.85em;
  padding: 5px;
  border-radius: 3px;
  transition: opacity 0.2s;
  display: flex;
  align-items: center;
  font-variant-numeric: tabular-nums;
}

.g-gantt-tooltip:before {
  content: "";
  position: absolute;
  top: 0;
  left: 10%;
  width: 0;
  height: 0;
  border: 10px solid transparent;
  border-bottom-color: black;
  border-top: 0;
  margin-left: -5px;
  margin-top: -5px;
}

.g-gantt-tooltip-color-dot {
  width: 8px;
  height: 8px;
  border-radius: 100%;
  margin-right: 4px;
}

.g-fade-enter-active,
.g-fade-leave-active {
  transition: opacity 0.3s ease;
}

.g-fade-enter-from,
.g-fade-leave-to {
  opacity: 0;
}
</style>


================================================
FILE: src/components/GGanttChart.vue
================================================
<template>
  <div>
    <div :class="[{ 'labels-in-column': !!labelColumnTitle }]">
      <g-gantt-label-column
        v-if="labelColumnTitle"
        :style="{
          width: labelColumnWidth
        }"
      >
        <template #label-column-title>
          <slot name="label-column-title" />
        </template>
        <template #label-column-row="{ label }">
          <slot name="label-column-row" :label="label" />
        </template>
      </g-gantt-label-column>
      <div
        ref="ganttChart"
        :class="['g-gantt-chart', { 'with-column': labelColumnTitle }]"
        :style="{ width, background: colors.background, fontFamily: font }"
      >
        <g-gantt-timeaxis v-if="!hideTimeaxis">
          <template #upper-timeunit="{ label, value, date }">
            <!-- expose upper-timeunit slot of g-gantt-timeaxis-->
            <slot name="upper-timeunit" :label="label" :value="value" :date="date" />
          </template>
          <template #timeunit="{ label, value, date }">
            <!-- expose timeunit slot of g-gantt-timeaxis-->
            <slot name="timeunit" :label="label" :value="value" :date="date" />
          </template>
        </g-gantt-timeaxis>
        <g-gantt-grid v-if="grid" :highlighted-units="highlightedUnits" />
        <g-gantt-current-time v-if="currentTime">
          <template #current-time-label>
            <slot name="current-time-label" />
          </template>
        </g-gantt-current-time>
        <div class="g-gantt-rows-container">
          <slot />
          <!-- the g-gantt-row components go here -->
        </div>
      </div>
    </div>
    <g-gantt-bar-tooltip :model-value="showTooltip || isDragging" :bar="tooltipBar">
      <template #default>
        <slot name="bar-tooltip" :bar="tooltipBar" />
      </template>
    </g-gantt-bar-tooltip>
  </div>
</template>

<script setup lang="ts">
import {
  computed,
  provide,
  ref,
  toRefs,
  useSlots,
  type ComputedRef,
  type Ref,
  type ToRefs
} from "vue"

import GGanttGrid from "./GGanttGrid.vue"
import GGanttLabelColumn from "./GGanttLabelColumn.vue"
import GGanttTimeaxis from "./GGanttTimeaxis.vue"
import GGanttBarTooltip from "./GGanttBarTooltip.vue"
import GGanttCurrentTime from "./GGanttCurrentTime.vue"

import type { GanttBarObject } from "../types"
import type { ColorSchemeKey } from "../color-schemes.js"

import { useElementSize } from "@vueuse/core"
import { DEFAULT_DATE_FORMAT } from "../composables/useDayjsHelper"
import { colorSchemes, type ColorScheme } from "../color-schemes.js"
import {
  CHART_ROWS_KEY,
  CONFIG_KEY,
  EMIT_BAR_EVENT_KEY,
  type ChartRow
} from "../provider/symbols.js"

export interface GGanttChartProps {
  chartStart: string | Date
  chartEnd: string | Date
  precision?: "hour" | "day" | "date" | "week" | "month"
  barStart: string
  barEnd: string
  currentTime?: boolean
  currentTimeLabel?: string
  dateFormat?: string | false
  width?: string
  hideTimeaxis?: boolean
  colorScheme?: ColorSchemeKey | ColorScheme
  grid?: boolean
  pushOnOverlap?: boolean
  noOverlap?: boolean
  rowHeight?: number
  highlightedUnits?: number[]
  font?: string
  labelColumnTitle?: string
  labelColumnWidth?: string
}

export type GGanttChartConfig = ToRefs<Required<GGanttChartProps>> & {
  colors: ComputedRef<ColorScheme>
  chartSize: {
    width: Ref<number>
    height: Ref<number>
  }
}

const props = withDefaults(defineProps<GGanttChartProps>(), {
  currentTimeLabel: "",
  dateFormat: DEFAULT_DATE_FORMAT,
  precision: "day",
  width: "100%",
  hideTimeaxis: false,
  colorScheme: "default",
  grid: false,
  pushOnOverlap: false,
  noOverlap: false,
  rowHeight: 40,
  highlightedUnits: () => [],
  font: "inherit",
  labelColumnTitle: "",
  labelColumnWidth: "150px"
})

const emit = defineEmits<{
  (e: "click-bar", value: { bar: GanttBarObject; e: MouseEvent; datetime?: string | Date }): void
  (
    e: "mousedown-bar",
    value: { bar: GanttBarObject; e: MouseEvent; datetime?: string | Date }
  ): void
  (e: "mouseup-bar", value: { bar: GanttBarObject; e: MouseEvent; datetime?: string | Date }): void
  (e: "dblclick-bar", value: { bar: GanttBarObject; e: MouseEvent; datetime?: string | Date }): void
  (e: "mouseenter-bar", value: { bar: GanttBarObject; e: MouseEvent }): void
  (e: "mouseleave-bar", value: { bar: GanttBarObject; e: MouseEvent }): void
  (e: "dragstart-bar", value: { bar: GanttBarObject; e: MouseEvent }): void
  (e: "drag-bar", value: { bar: GanttBarObject; e: MouseEvent }): void
  (
    e: "dragend-bar",
    value: {
      bar: GanttBarObject
      e: MouseEvent
      movedBars?: Map<GanttBarObject, { oldStart: string; oldEnd: string }>
    }
  ): void
  (
    e: "contextmenu-bar",
    value: { bar: GanttBarObject; e: MouseEvent; datetime?: string | Date }
  ): void
}>()

const { width, font, colorScheme } = toRefs(props)

const slots = useSlots()
const colors = computed(() =>
  typeof colorScheme.value !== "string"
    ? colorScheme.value
    : colorSchemes[colorScheme.value as ColorSchemeKey] || colorSchemes.default
)
const getChartRows = () => {
  const defaultSlot = slots.default?.()
  const allBars: ChartRow[] = []

  if (!defaultSlot) {
    return allBars
  }
  defaultSlot.forEach((child) => {
    if (child.props?.bars) {
      const { label, bars } = child.props
      allBars.push({ label, bars })
      // if using v-for to generate rows, rows will be children of a single "fragment" v-node:
    } else if (Array.isArray(child.children)) {
      child.children.forEach((grandchild) => {
        const granchildNode = grandchild as {
          props?: ChartRow
        }
        if (granchildNode?.props?.bars) {
          const { label, bars } = granchildNode.props
          allBars.push({ label, bars })
        }
      })
    }
  })
  return allBars
}

const showTooltip = ref(false)
const isDragging = ref(false)
const tooltipBar = ref<GanttBarObject | undefined>(undefined)
let tooltipTimeoutId: ReturnType<typeof setTimeout>
const initTooltip = (bar: GanttBarObject) => {
  if (tooltipTimeoutId) {
    clearTimeout(tooltipTimeoutId)
  }
  tooltipTimeoutId = setTimeout(() => {
    showTooltip.value = true
  }, 800)
  tooltipBar.value = bar
}

const clearTooltip = () => {
  clearTimeout(tooltipTimeoutId)
  showTooltip.value = false
}

const emitBarEvent = (
  e: MouseEvent,
  bar: GanttBarObject,
  datetime?: string | Date,
  movedBars?: Map<GanttBarObject, { oldStart: string; oldEnd: string }>
) => {
  switch (e.type) {
    case "click":
      emit("click-bar", { bar, e, datetime })
      break
    case "mousedown":
      emit("mousedown-bar", { bar, e, datetime })
      break
    case "mouseup":
      emit("mouseup-bar", { bar, e, datetime })
      break
    case "dblclick":
      emit("dblclick-bar", { bar, e, datetime })
      break
    case "mouseenter":
      initTooltip(bar)
      emit("mouseenter-bar", { bar, e })
      break
    case "mouseleave":
      clearTooltip()
      emit("mouseleave-bar", { bar, e })
      break
    case "dragstart":
      isDragging.value = true
      emit("dragstart-bar", { bar, e })
      break
    case "drag":
      emit("drag-bar", { bar, e })
      break
    case "dragend":
      isDragging.value = false
      emit("dragend-bar", { bar, e, movedBars })
      break
    case "contextmenu":
      emit("contextmenu-bar", { bar, e, datetime })
      break
  }
}

const ganttChart = ref<HTMLElement | null>(null)
const chartSize = useElementSize(ganttChart)

provide(CHART_ROWS_KEY, getChartRows)
provide(CONFIG_KEY, {
  ...toRefs(props),
  colors,
  chartSize
})
provide(EMIT_BAR_EVENT_KEY, emitBarEvent)
</script>

<style>
.g-gantt-chart {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
  -webkit-touch-callout: none;
  user-select: none;
  font-variant-numeric: tabular-nums;
  border-radius: 5px;
}

.with-column {
  border-top-left-radius: 0px;
  border-bottom-left-radius: 0px;
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
}

.g-gantt-rows-container {
  position: relative;
}

.labels-in-column {
  display: flex;
  flex-direction: row;
}
</style>


================================================
FILE: src/components/GGanttCurrentTime.vue
================================================
<template>
  <div
    class="g-grid-current-time"
    :style="{
      left: `${xDist}px`
    }"
  >
    <div
      class="g-grid-current-time-marker"
      :style="{
        border: `1px dashed ${colors.markerCurrentTime}`
      }"
    />
    <span class="g-grid-current-time-text" :style="{ color: colors.markerCurrentTime }">
      <slot name="current-time-label">
        {{ currentTimeLabel }}
      </slot>
    </span>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue"
import useTimePositionMapping from "../composables/useTimePositionMapping.js"
import dayjs from "dayjs"
import provideConfig from "../provider/provideConfig.js"

const { mapTimeToPosition } = useTimePositionMapping()
const currentMoment = ref(dayjs())
const { colors, dateFormat, currentTimeLabel } = provideConfig()
const xDist = computed(() => {
  const format = dateFormat.value || "YYYY-MM-DD HH:mm"
  return mapTimeToPosition(dayjs(currentMoment.value, format).format(format))
})
</script>

<style>
.g-grid-current-time {
  position: absolute;
  height: 100%;
  display: flex;
  z-index: 5;
  pointer-events: none;
}

.g-grid-current-time-marker {
  width: 0px;
  height: calc(100% - 2px);
  display: flex;
}

.g-grid-current-time-text {
  font-size: x-small;
}
</style>


================================================
FILE: src/components/GGanttGrid.vue
================================================
<template>
  <div class="g-grid-container">
    <div
      v-for="{ label, value, width } in timeaxisUnits.lowerUnits"
      :key="label"
      class="g-grid-line"
      :style="{
        width,
        background: highlightedUnits?.includes(Number(value)) ? colors.hoverHighlight : undefined
      }"
    />
  </div>
</template>

<script setup lang="ts">
import provideConfig from "../provider/provideConfig.js"
import useTimeaxisUnits from "../composables/useTimeaxisUnits.js"

defineProps<{
  highlightedUnits?: number[]
}>()

const { colors } = provideConfig()
const { timeaxisUnits } = useTimeaxisUnits()
</script>

<style>
.g-grid-container {
  position: absolute;
  top: 0;
  left: 0%;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: space-between;
}

.g-grid-line {
  width: 1px;
  height: 100%;
  border-left: 1px solid #eaeaea;
}
</style>


================================================
FILE: src/components/GGanttLabelColumn.vue
================================================
<template>
  <div class="g-label-column" :style="{ fontFamily: font, color: colors.text }">
    <slot name="label-column-title">
      <div class="g-label-column-header" :style="{ background: colors.primary }">
        {{ labelColumnTitle }}
      </div>
    </slot>
    <div class="g-label-column-rows">
      <div
        v-for="({ label }, index) in getChartRows()"
        :key="`${label}_${index}`"
        class="g-label-column-row"
        :style="{
          background: index % 2 === 0 ? colors.ternary : colors.quartenary,
          height: `${rowHeight}px`
        }"
      >
        <slot name="label-column-row" :label="label">
          <span>{{ label }}</span>
        </slot>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import provideGetChartRows from "../provider/provideGetChartRows"
import provideConfig from "../provider/provideConfig.js"

const { font, colors, labelColumnTitle, rowHeight } = provideConfig()
const getChartRows = provideGetChartRows()
</script>

<style>
.g-label-column {
  display: flex;
  align-items: center;
  flex-direction: column;
  color: rgb(64, 64, 64);
  font-variant-numeric: tabular-nums;
  font-size: 0.9em;
}

.g-label-column-header {
  width: 100%;
  height: 80px;
  min-height: 80px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  border-top-left-radius: 5px;
}

.g-label-column-rows {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  border-bottom-left-radius: 5px;
}

.g-label-column-row {
  width: 100%;
  height: 100%;
  display: flex;
  padding: 0.1rem 0.3rem;
  overflow: hidden;
  white-space: normal;
  box-sizing: border-box;
  text-align: center;
  align-items: center;
  justify-content: center;
}

.g-label-column-row:last-child {
  border-bottom-left-radius: 5px;
}
</style>


================================================
FILE: src/components/GGanttRow.vue
================================================
<template>
  <div
    class="g-gantt-row"
    :style="rowStyle"
    @dragover.prevent="isHovering = true"
    @dragleave="isHovering = false"
    @drop="onDrop($event)"
    @mouseover="isHovering = true"
    @mouseleave="isHovering = false"
  >
    <div
      v-if="!isBlank(label) && !labelColumnTitle"
      class="g-gantt-row-label"
      :style="{ background: colors.primary, color: colors.text }"
    >
      <slot name="label">
        {{ label }}
      </slot>
    </div>
    <div ref="barContainer" class="g-gantt-row-bars-container" v-bind="$attrs">
      <transition-group name="bar-transition" tag="div">
        <g-gantt-bar v-for="bar in bars" :key="bar.ganttBarConfig.id" :bar="bar">
          <slot name="bar-label" :bar="bar" />
        </g-gantt-bar>
      </transition-group>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, type Ref, toRefs, computed, type StyleValue, provide } from "vue"

import useTimePositionMapping from "../composables/useTimePositionMapping.js"
import provideConfig from "../provider/provideConfig.js"
import type { GanttBarObject } from "../types"
import GGanttBar from "./GGanttBar.vue"
import { BAR_CONTAINER_KEY } from "../provider/symbols"

const props = defineProps<{
  label: string
  bars: GanttBarObject[]
  highlightOnHover?: boolean
}>()

const emit = defineEmits<{
  (e: "drop", value: { e: MouseEvent; datetime: string | Date }): void
}>()

const { rowHeight, colors, labelColumnTitle } = provideConfig()
const { highlightOnHover } = toRefs(props)
const isHovering = ref(false)

const rowStyle = computed(() => {
  return {
    height: `${rowHeight.value}px`,
    background: highlightOnHover?.value && isHovering.value ? colors.value.hoverHighlight : null
  } as StyleValue
})

const { mapPositionToTime } = useTimePositionMapping()
const barContainer: Ref<HTMLElement | null> = ref(null)

provide(BAR_CONTAINER_KEY, barContainer)

const onDrop = (e: MouseEvent) => {
  const container = barContainer.value?.getBoundingClientRect()
  if (!container) {
    console.error("Vue-Ganttastic: failed to find bar container element for row.")
    return
  }
  const xPos = e.clientX - container.left
  const datetime = mapPositionToTime(xPos)
  emit("drop", { e, datetime })
}

const isBlank = (str: string) => {
  return (!str || /^\s*$/.test(str))
}

</script>

<style>
.g-gantt-row {
  width: 100%;
  transition: background 0.4s;
  position: relative;
}

.g-gantt-row > .g-gantt-row-bars-container {
  position: relative;
  border-top: 1px solid #eaeaea;
  width: 100%;
  border-bottom: 1px solid #eaeaea;
}

.g-gantt-row-label {
  position: absolute;
  top: 0;
  left: 0px;
  padding: 0px 8px;
  display: flex;
  align-items: center;
  height: 60%;
  min-height: 20px;
  font-size: 0.8em;
  font-weight: bold;
  border-bottom-right-radius: 6px;
  background: #f2f2f2;
  z-index: 3;
  box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.6);
}

.bar-transition-leave-active,
.bar-transition-enter-active {
  transition: all 0.2s;
}

.bar-transition-enter-from,
.bar-transition-leave-to {
  transform: scale(0.8);
  opacity: 0;
}
</style>


================================================
FILE: src/components/GGanttTimeaxis.vue
================================================
<template>
  <div class="g-timeaxis">
    <div class="g-timeunits-container">
      <div
        v-for="({ label, value, date, width }, index) in timeaxisUnits.upperUnits"
        :key="label"
        class="g-upper-timeunit"
        :style="{
          background: index % 2 === 0 ? colors.primary : colors.secondary,
          color: colors.text,
          width
        }"
      >
        <slot name="upper-timeunit" :label="label" :value="value" :date="date">
          {{ label }}
        </slot>
      </div>
    </div>

    <div class="g-timeunits-container">
      <div
        v-for="({ label, value, date, width }, index) in timeaxisUnits.lowerUnits"
        :key="label"
        class="g-timeunit"
        :style="{
          background: index % 2 === 0 ? colors.ternary : colors.quartenary,
          color: colors.text,
          flexDirection: precision === 'hour' ? 'column' : 'row',
          alignItems: precision === 'hour' ? '' : 'center',
          width
        }"
      >
        <slot name="timeunit" :label="label" :value="value" :date="date">
          {{ label }}
        </slot>
        <div
          v-if="precision === 'hour'"
          class="g-timeaxis-hour-pin"
          :style="{ background: colors.text }"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import provideConfig from "../provider/provideConfig.js"
import useTimeaxisUnits from "../composables/useTimeaxisUnits.js"

const { precision, colors } = provideConfig()
const { timeaxisUnits } = useTimeaxisUnits()
</script>

<style>
.g-timeaxis {
  position: sticky;
  top: 0;
  width: 100%;
  height: 80px;
  background: white;
  z-index: 4;
  display: flex;
  flex-direction: column;
}

.g-timeunits-container {
  display: flex;
  width: 100%;
  height: 50%;
}

.g-timeunit {
  height: 100%;
  font-size: 65%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.g-upper-timeunit {
  display: flex;
  height: 100%;
  justify-content: center;
  align-items: center;
}

.g-timeaxis-hour-pin {
  width: 1px;
  height: 10px;
}
</style>


================================================
FILE: src/composables/createBarDrag.ts
================================================
import { ref } from "vue"
import type { GGanttChartConfig } from "../components/GGanttChart.vue"
import provideConfig from "../provider/provideConfig.js"

import type { GanttBarObject } from "../types"
import useDayjsHelper from "./useDayjsHelper.js"
import useTimePositionMapping from "./useTimePositionMapping.js"

export default function createBarDrag(
  bar: GanttBarObject,
  onDrag: (e: MouseEvent, bar: GanttBarObject) => void = () => null,
  onEndDrag: (e: MouseEvent, bar: GanttBarObject) => void = () => null,
  config: GGanttChartConfig = provideConfig()
) {
  const { barStart, barEnd, pushOnOverlap } = config

  const isDragging = ref(false)
  let cursorOffsetX = 0
  let dragCallBack: (e: MouseEvent) => void

  const { mapPositionToTime } = useTimePositionMapping(config)
  const { toDayjs } = useDayjsHelper(config)

  const initDrag = (e: MouseEvent) => {
    const barElement = document.getElementById(bar.ganttBarConfig.id)
    if (!barElement) {
      return
    }

    cursorOffsetX = e.clientX - (barElement.getBoundingClientRect().left || 0)
    const mousedownType = (e.target as Element).className
    switch (mousedownType) {
      case "g-gantt-bar-handle-left":
        document.body.style.cursor = "ew-resize"
        dragCallBack = dragByLeftHandle
        break
      case "g-gantt-bar-handle-right":
        document.body.style.cursor = "ew-resize"
        dragCallBack = dragByRightHandle
        break
      default:
        dragCallBack = drag
    }
    isDragging.value = true
    window.addEventListener("mousemove", dragCallBack)
    window.addEventListener("mouseup", endDrag)
  }

  const getBarElements = () => {
    const barElement = document.getElementById(bar.ganttBarConfig.id)
    const barContainer = barElement?.closest(".g-gantt-row-bars-container")?.getBoundingClientRect()
    return { barElement, barContainer }
  }

  const drag = (e: MouseEvent) => {
    const { barElement, barContainer } = getBarElements()
    if (!barElement || !barContainer) {
      return
    }

    const barWidth = barElement.getBoundingClientRect().width
    const xStart = e.clientX - barContainer.left - cursorOffsetX
    const xEnd = xStart + barWidth
    if (isOutOfRange(xStart, xEnd)) {
      return
    }
    bar[barStart.value] = mapPositionToTime(xStart)
    bar[barEnd.value] = mapPositionToTime(xEnd)
    onDrag(e, bar)
  }

  const dragByLeftHandle = (e: MouseEvent) => {
    const { barElement, barContainer } = getBarElements()
    if (!barElement || !barContainer) {
      return
    }

    const xStart = e.clientX - barContainer.left
    const newBarStart = mapPositionToTime(xStart)
    if (toDayjs(newBarStart).isSameOrAfter(toDayjs(bar, "end"))) {
      return
    }
    bar[barStart.value] = newBarStart
    onDrag(e, bar)
  }

  const dragByRightHandle = (e: MouseEvent) => {
    const { barElement, barContainer } = getBarElements()
    if (!barElement || !barContainer) {
      return
    }

    const xEnd = e.clientX - barContainer.left
    const newBarEnd = mapPositionToTime(xEnd)
    if (toDayjs(newBarEnd).isSameOrBefore(toDayjs(bar, "start"))) {
      return
    }
    bar[barEnd.value] = newBarEnd
    onDrag(e, bar)
  }

  const isOutOfRange = (xStart?: number, xEnd?: number) => {
    if (!pushOnOverlap.value) {
      return false
    }
    const dragLimitLeft = bar.ganttBarConfig.dragLimitLeft
    const dragLimitRight = bar.ganttBarConfig.dragLimitRight

    return (
      (xStart && dragLimitLeft != null && xStart < dragLimitLeft) ||
      (xEnd && dragLimitRight != null && xEnd > dragLimitRight)
    )
  }

  const endDrag = (e: MouseEvent) => {
    isDragging.value = false
    document.body.style.cursor = ""
    window.removeEventListener("mousemove", dragCallBack)
    window.removeEventListener("mouseup", endDrag)
    onEndDrag(e, bar)
  }

  return {
    isDragging,
    initDrag
  }
}


================================================
FILE: src/composables/useBarDragLimit.ts
================================================
import type { GanttBarObject } from "../types"
import provideConfig from "../provider/provideConfig.js"
import provideGetChartRows from "../provider/provideGetChartRows.js"

export default function useBarDragLimit() {
  const { pushOnOverlap } = provideConfig()
  const getChartRows = provideGetChartRows()

  const getBarsFromBundle = (bundle?: string) => {
    const res: GanttBarObject[] = []
    if (bundle != null) {
      getChartRows().forEach((row) => {
        row.bars.forEach((bar) => {
          if (bar.ganttBarConfig.bundle === bundle) {
            res.push(bar)
          }
        })
      })
    }
    return res
  }

  const setDragLimitsOfGanttBar = (bar: GanttBarObject) => {
    if (!pushOnOverlap.value || bar.ganttBarConfig.pushOnOverlap === false) {
      return
    }
    for (const sideValue of ["left", "right"]) {
      const side = sideValue as "left" | "right"
      const { gapDistanceSoFar, bundleBarsAndGapDist } = countGapDistanceToNextImmobileBar(
        bar,
        0,
        side
      )
      let totalGapDistance = gapDistanceSoFar
      const bundleBarsOnPath = bundleBarsAndGapDist
      if (!bundleBarsOnPath) {
        continue
      }

      for (let i = 0; i < bundleBarsOnPath.length; i++) {
        const barFromBundle = bundleBarsOnPath[i].bar
        const gapDist = bundleBarsOnPath[i].gapDistance
        const otherBarsFromBundle = getBarsFromBundle(barFromBundle.ganttBarConfig.bundle).filter(
          (otherBar) => otherBar !== barFromBundle
        )
        otherBarsFromBundle.forEach((otherBar) => {
          const nextGapDistanceAndBars = countGapDistanceToNextImmobileBar(otherBar, gapDist, side)
          const newGapDistance = nextGapDistanceAndBars.gapDistanceSoFar
          const newBundleBars = nextGapDistanceAndBars.bundleBarsAndGapDist
          if (newGapDistance != null && (!totalGapDistance || newGapDistance < totalGapDistance)) {
            totalGapDistance = newGapDistance
          }
          newBundleBars.forEach((newBundleBar) => {
            if (!bundleBarsOnPath.find((barAndGap) => barAndGap.bar === newBundleBar.bar)) {
              bundleBarsOnPath.push(newBundleBar)
            }
          })
        })
      }
      const barElem = document.getElementById(bar.ganttBarConfig.id) as HTMLElement
      if (totalGapDistance != null && side === "left") {
        bar.ganttBarConfig.dragLimitLeft = barElem.offsetLeft - totalGapDistance
      } else if (totalGapDistance != null && side === "right") {
        bar.ganttBarConfig.dragLimitRight =
          barElem.offsetLeft + barElem.offsetWidth + totalGapDistance
      }
    }
    // all bars from the bundle of the clicked bar need to have the same drag limit:
    const barsFromBundleOfClickedBar = getBarsFromBundle(bar.ganttBarConfig.bundle)
    barsFromBundleOfClickedBar.forEach((barFromBundle) => {
      barFromBundle.ganttBarConfig.dragLimitLeft = bar.ganttBarConfig.dragLimitLeft
      barFromBundle.ganttBarConfig.dragLimitRight = bar.ganttBarConfig.dragLimitRight
    })
  }

  // returns the gap distance to the next immobile bar
  // in the row where the given bar (parameter) is (added to gapDistanceSoFar)
  // and a list of all bars on that path that belong to a bundle
  const countGapDistanceToNextImmobileBar = (
    bar: GanttBarObject,
    gapDistanceSoFar = 0,
    side: "left" | "right"
  ) => {
    const bundleBarsAndGapDist = bar.ganttBarConfig.bundle
      ? [{ bar, gapDistance: gapDistanceSoFar }]
      : []
    let currentBar = bar
    let nextBar = getNextGanttBar(currentBar, side)
    // left side:
    if (side === "left") {
      while (nextBar) {
        const currentBarElem = document.getElementById(currentBar.ganttBarConfig.id) as HTMLElement
        const nextBarElem = document.getElementById(nextBar.ganttBarConfig.id) as HTMLElement
        const nextBarOffsetRight = nextBarElem.offsetLeft + nextBarElem.offsetWidth
        gapDistanceSoFar += currentBarElem.offsetLeft - nextBarOffsetRight
        if (nextBar.ganttBarConfig.immobile) {
          return { gapDistanceSoFar, bundleBarsAndGapDist }
        } else if (nextBar.ganttBarConfig.bundle) {
          bundleBarsAndGapDist.push({
            bar: nextBar,
            gapDistance: gapDistanceSoFar
          })
        }
        currentBar = nextBar
        nextBar = getNextGanttBar(nextBar, "left")
      }
    }
    if (side === "right") {
      while (nextBar) {
        const currentBarElem = document.getElementById(currentBar.ganttBarConfig.id) as HTMLElement
        const nextBarElem = document.getElementById(nextBar.ganttBarConfig.id) as HTMLElement
        const currentBarOffsetRight = currentBarElem.offsetLeft + currentBarElem.offsetWidth
        gapDistanceSoFar += nextBarElem.offsetLeft - currentBarOffsetRight
        if (nextBar.ganttBarConfig.immobile) {
          return { gapDistanceSoFar, bundleBarsAndGapDist }
        } else if (nextBar.ganttBarConfig.bundle) {
          bundleBarsAndGapDist.push({
            bar: nextBar,
            gapDistance: gapDistanceSoFar
          })
        }
        currentBar = nextBar
        nextBar = getNextGanttBar(nextBar, "right")
      }
    }
    return { gapDistanceSoFar: null, bundleBarsAndGapDist }
  }

  const getNextGanttBar = (bar: GanttBarObject, side: "left" | "right") => {
    const barElem = document.getElementById(bar.ganttBarConfig.id) as HTMLElement
    const allBarsInRow = getChartRows().find((row) => row.bars.includes(bar))?.bars ?? []
    let allBarsLeftOrRight = []
    if (side === "left") {
      allBarsLeftOrRight = allBarsInRow.filter((otherBar) => {
        const otherBarElem = document.getElementById(otherBar.ganttBarConfig.id) as HTMLElement
        return (
          otherBarElem &&
          otherBarElem.offsetLeft < barElem.offsetLeft &&
          otherBar.ganttBarConfig.pushOnOverlap !== false
        )
      })
    } else {
      allBarsLeftOrRight = allBarsInRow.filter((otherBar) => {
        const otherBarElem = document.getElementById(otherBar.ganttBarConfig.id) as HTMLElement
        return (
          otherBarElem &&
          otherBarElem.offsetLeft > barElem.offsetLeft &&
          otherBar.ganttBarConfig.pushOnOverlap !== false
        )
      })
    }
    if (allBarsLeftOrRight.length > 0) {
      return allBarsLeftOrRight.reduce((bar1, bar2) => {
        const bar1Elem = document.getElementById(bar1.ganttBarConfig.id) as HTMLElement
        const bar2Elem = document.getElementById(bar2.ganttBarConfig.id) as HTMLElement
        const bar1Dist = Math.abs(bar1Elem.offsetLeft - barElem.offsetLeft)
        const bar2Dist = Math.abs(bar2Elem.offsetLeft - barElem.offsetLeft)
        return bar1Dist < bar2Dist ? bar1 : bar2
      }, allBarsLeftOrRight[0])
    } else {
      return null
    }
  }

  return {
    setDragLimitsOfGanttBar
  }
}


================================================
FILE: src/composables/useBarDragManagement.ts
================================================
import type { GanttBarObject } from "../types"

import createBarDrag from "./createBarDrag.js"
import useDayjsHelper from "./useDayjsHelper.js"
import provideConfig from "../provider/provideConfig.js"
import provideGetChartRows from "../provider/provideGetChartRows.js"
import provideEmitBarEvent from "../provider/provideEmitBarEvent.js"

export default function useBarDragManagement() {
  const config = provideConfig()
  const getChartRows = provideGetChartRows()
  const emitBarEvent = provideEmitBarEvent()
  const { pushOnOverlap, barStart, barEnd, noOverlap, dateFormat } = config

  const movedBarsInDrag = new Map<GanttBarObject, { oldStart: string; oldEnd: string }>()

  const { toDayjs, format } = useDayjsHelper()

  const initDragOfBar = (bar: GanttBarObject, e: MouseEvent) => {
    const { initDrag } = createBarDrag(bar, onDrag, onEndDrag, config)
    emitBarEvent({ ...e, type: "dragstart" }, bar)
    initDrag(e)
    addBarToMovedBars(bar)
  }

  const initDragOfBundle = (mainBar: GanttBarObject, e: MouseEvent) => {
    const bundle = mainBar.ganttBarConfig.bundle
    if (bundle == null) {
      return
    }
    getChartRows().forEach((row) => {
      row.bars.forEach((bar) => {
        if (bar.ganttBarConfig.bundle === bundle) {
          const dragEndHandler = bar === mainBar ? onEndDrag : () => null
          const { initDrag } = createBarDrag(bar, onDrag, dragEndHandler, config)
          initDrag(e)
          addBarToMovedBars(bar)
        }
      })
    })
    emitBarEvent({ ...e, type: "dragstart" }, mainBar)
  }

  const onDrag = (e: MouseEvent, bar: GanttBarObject) => {
    emitBarEvent({ ...e, type: "drag" }, bar)
    fixOverlaps(bar)
  }

  const fixOverlaps = (ganttBar: GanttBarObject) => {
    if (!pushOnOverlap?.value) {
      return
    }
    let currentBar = ganttBar
    let { overlapBar, overlapType } = getOverlapBarAndType(currentBar)
    while (overlapBar) {
      addBarToMovedBars(overlapBar)
      const currentBarStart = toDayjs(currentBar[barStart.value])
      const currentBarEnd = toDayjs(currentBar[barEnd.value])
      const overlapBarStart = toDayjs(overlapBar[barStart.value])
      const overlapBarEnd = toDayjs(overlapBar[barEnd.value])
      let minuteDiff: number
      switch (overlapType) {
        case "left":
          minuteDiff = overlapBarEnd.diff(currentBarStart, "minutes", true)
          overlapBar[barEnd.value] = format(currentBar[barStart.value], dateFormat.value)
          overlapBar[barStart.value] = format(
            overlapBarStart.subtract(minuteDiff, "minutes"),
            dateFormat.value
          )
          break
        case "right":
          minuteDiff = currentBarEnd.diff(overlapBarStart, "minutes", true)
          overlapBar[barStart.value] = format(currentBarEnd, dateFormat.value)
          overlapBar[barEnd.value] = format(
            overlapBarEnd.add(minuteDiff, "minutes"),
            dateFormat.value
          )
          break
        default:
          console.warn(
            "Vue-Ganttastic: One bar is inside of the other one! This should never occur while push-on-overlap is active!"
          )
          return
      }
      if (overlapBar && (overlapType === "left" || overlapType === "right")) {
        moveBundleOfPushedBarByMinutes(overlapBar, minuteDiff, overlapType)
      }
      currentBar = overlapBar
      ;({ overlapBar, overlapType } = getOverlapBarAndType(overlapBar))
    }
  }

  const getOverlapBarAndType = (ganttBar: GanttBarObject) => {
    let overlapLeft, overlapRight, overlapInBetween
    const allBarsInRow = getChartRows().find((row) => row.bars.includes(ganttBar))?.bars ?? []
    const ganttBarStart = toDayjs(ganttBar[barStart.value])
    const ganttBarEnd = toDayjs(ganttBar[barEnd.value])
    const overlapBar = allBarsInRow.find((otherBar) => {
      if (otherBar === ganttBar) {
        return false
      }
      const otherBarStart = toDayjs(otherBar[barStart.value])
      const otherBarEnd = toDayjs(otherBar[barEnd.value])
      overlapLeft = ganttBarStart.isBetween(otherBarStart, otherBarEnd)
      overlapRight = ganttBarEnd.isBetween(otherBarStart, otherBarEnd)
      overlapInBetween =
        otherBarStart.isBetween(ganttBarStart, ganttBarEnd) ||
        otherBarEnd.isBetween(ganttBarStart, ganttBarEnd)
      return overlapLeft || overlapRight || overlapInBetween
    })
    const overlapType = overlapLeft
      ? "left"
      : overlapRight
      ? "right"
      : overlapInBetween
      ? "between"
      : null
    return { overlapBar, overlapType }
  }

  const moveBundleOfPushedBarByMinutes = (
    pushedBar: GanttBarObject,
    minutes: number,
    direction: "left" | "right"
  ) => {
    addBarToMovedBars(pushedBar)
    if (!pushedBar.ganttBarConfig.bundle) {
      return
    }
    getChartRows().forEach((row) => {
      row.bars.forEach((bar) => {
        if (bar.ganttBarConfig.bundle === pushedBar.ganttBarConfig.bundle && bar !== pushedBar) {
          addBarToMovedBars(bar)
          moveBarByMinutes(bar, minutes, direction)
        }
      })
    })
  }

  const moveBarByMinutes = (bar: GanttBarObject, minutes: number, direction: "left" | "right") => {
    switch (direction) {
      case "left":
        bar[barStart.value] = format(
          toDayjs(bar, "start").subtract(minutes, "minutes"),
          dateFormat.value
        )
        bar[barEnd.value] = format(
          toDayjs(bar, "end").subtract(minutes, "minutes"),
          dateFormat.value
        )
        break
      case "right":
        bar[barStart.value] = format(
          toDayjs(bar, "start").add(minutes, "minutes"),
          dateFormat.value
        )
        bar[barEnd.value] = format(toDayjs(bar, "end").add(minutes, "minutes"), dateFormat.value)
    }
    fixOverlaps(bar)
  }

  const onEndDrag = (e: MouseEvent, bar: GanttBarObject) => {
    snapBackAllMovedBarsIfNeeded()
    const ev = {
      ...e,
      type: "dragend"
    }
    emitBarEvent(ev, bar, undefined, new Map(movedBarsInDrag))
    movedBarsInDrag.clear()
  }

  const addBarToMovedBars = (bar: GanttBarObject) => {
    if (!movedBarsInDrag.has(bar)) {
      const oldStart = bar[barStart.value]
      const oldEnd = bar[barEnd.value]
      movedBarsInDrag.set(bar, { oldStart, oldEnd })
    }
  }

  const snapBackAllMovedBarsIfNeeded = () => {
    if (pushOnOverlap.value || !noOverlap.value) {
      return
    }

    let isAnyOverlap = false
    movedBarsInDrag.forEach((_, bar) => {
      const { overlapBar } = getOverlapBarAndType(bar)
      if (overlapBar != null) {
        isAnyOverlap = true
      }
    })
    if (!isAnyOverlap) {
      return
    }
    movedBarsInDrag.forEach(({ oldStart, oldEnd }, bar) => {
      bar[barStart.value] = oldStart
      bar[barEnd.value] = oldEnd
    })
  }

  return {
    initDragOfBar,
    initDragOfBundle
  }
}


================================================
FILE: src/composables/useDayjsHelper.ts
================================================
import dayjs, { type Dayjs } from "dayjs"
import { computed } from "vue"

import type { GGanttChartConfig } from "../components/GGanttChart.vue"
import type { GanttBarObject } from "../types"
import provideConfig from "../provider/provideConfig.js"

export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD HH:mm"

export default function useDayjsHelper(config: GGanttChartConfig = provideConfig()) {
  const { chartStart, chartEnd, barStart, barEnd, dateFormat } = config

  const chartStartDayjs = computed(() => toDayjs(chartStart.value))
  const chartEndDayjs = computed(() => toDayjs(chartEnd.value))

  const toDayjs = (input: string | Date | GanttBarObject, startOrEnd?: "start" | "end") => {
    let value
    if (startOrEnd !== undefined && typeof input !== "string" && !(input instanceof Date)) {
      value = startOrEnd === "start" ? input[barStart.value] : input[barEnd.value]
    }
    if (typeof input === "string") {
      value = input
    } else if (input instanceof Date) {
      return dayjs(input)
    }
    const format = dateFormat.value || DEFAULT_DATE_FORMAT
    return dayjs(value, format, true)
  }

  const format = (input: string | Date | Dayjs, pattern?: string | false) => {
    if (pattern === false) {
      return input instanceof Date ? input : dayjs(input).toDate()
    }
    const inputDayjs = typeof input === "string" || input instanceof Date ? toDayjs(input) : input

    return inputDayjs.format(pattern)
  }

  return {
    chartStartDayjs,
    chartEndDayjs,
    toDayjs,
    format
  }
}


================================================
FILE: src/composables/useTimePositionMapping.ts
================================================
import type { GGanttChartConfig } from "../components/GGanttChart.vue"
import { computed } from "vue"

import useDayjsHelper from "./useDayjsHelper.js"
import provideConfig from "../provider/provideConfig.js"

export default function useTimePositionMapping(config: GGanttChartConfig = provideConfig()) {
  const { dateFormat, chartSize } = config
  const { chartStartDayjs, chartEndDayjs, toDayjs, format } = useDayjsHelper(config)

  const totalNumOfMinutes = computed(() => {
    return chartEndDayjs.value.diff(chartStartDayjs.value, "minutes")
  })

  const mapTimeToPosition = (time: string) => {
    const width = chartSize.width.value || 0
    const diffFromStart = toDayjs(time).diff(chartStartDayjs.value, "minutes", true)
    return Math.ceil((diffFromStart / totalNumOfMinutes.value) * width)
  }

  const mapPositionToTime = (xPos: number) => {
    const width = chartSize.width.value || 0
    const diffFromStart = (xPos / width) * totalNumOfMinutes.value
    return format(chartStartDayjs.value.add(diffFromStart, "minutes"), dateFormat.value)
  }

  return {
    mapTimeToPosition,
    mapPositionToTime
  }
}


================================================
FILE: src/composables/useTimeaxisUnits.ts
================================================
import { computed } from "vue"
import useDayjsHelper from "./useDayjsHelper.js"
import provideConfig from "../provider/provideConfig.js"

export default function useTimeaxisUnits() {
  const { precision } = provideConfig()
  const { chartStartDayjs, chartEndDayjs } = useDayjsHelper()

  const upperPrecision = computed(() => {
    switch (precision?.value) {
      case "hour":
        return "day"
      case "day":
        return "month"
      case "date":
      case "week":
        return "month"
      case "month":
        return "year"
      default:
        throw new Error(
          "Precision prop incorrect. Must be one of the following: 'hour', 'day', 'date', 'week', 'month'"
        )
    }
  })

  const lowerPrecision = computed(() => {
    switch (precision.value) {
      case "date":
        return "day"
      case "week":
        return "isoWeek"
      default:
        return precision.value
    }
  })

  const displayFormats = {
    hour: "HH",
    date: "DD.MMM",
    day: "DD.MMM",
    week: "WW",
    month: "MMMM YYYY",
    year: "YYYY"
  }

  const timeaxisUnits = computed(() => {
    const upperUnits: { label: string; value?: string; date: Date; width?: string }[] = []
    const lowerUnits: { label: string; value?: string; date: Date; width?: string }[] = []
    const totalMinutes = chartEndDayjs.value.diff(chartStartDayjs.value, "minutes", true)
    const upperUnit = upperPrecision.value
    const lowerUnit = lowerPrecision.value
    let currentUpperUnit = chartStartDayjs.value
    let currentLowerUnit = chartStartDayjs.value

    while (currentLowerUnit.isSameOrBefore(chartEndDayjs.value)) {
      const endCurrentLowerUnit = currentLowerUnit.endOf(lowerUnit)
      const isLastItem = endCurrentLowerUnit.isAfter(chartEndDayjs.value)

      const lowerWidth = isLastItem
        ? (chartEndDayjs.value.diff(currentLowerUnit, "minutes", true) / totalMinutes) * 100
        : (endCurrentLowerUnit.diff(currentLowerUnit, "minutes", true) / totalMinutes) * 100

      lowerUnits.push({
        label: currentLowerUnit.format(displayFormats[precision?.value]),
        value: String(currentLowerUnit),
        date: currentLowerUnit.toDate(),
        width: String(lowerWidth) + "%"
      })
      currentLowerUnit = endCurrentLowerUnit
        .add(1, lowerUnit === "isoWeek" ? "week" : lowerUnit)
        .startOf(lowerUnit)
    }
    while (currentUpperUnit.isSameOrBefore(chartEndDayjs.value)) {
      const endCurrentUpperUnit = currentUpperUnit.endOf(upperUnit)
      const isLastItem = endCurrentUpperUnit.isAfter(chartEndDayjs.value)

      const upperWidth = isLastItem
        ? (chartEndDayjs.value.diff(currentUpperUnit, "minutes", true) / totalMinutes) * 100
        : (endCurrentUpperUnit.diff(currentUpperUnit, "minutes", true) / totalMinutes) * 100

      upperUnits.push({
        label: currentUpperUnit.format(displayFormats[upperUnit]),
        value: String(currentUpperUnit),
        date: currentUpperUnit.toDate(),
        width: String(upperWidth) + "%"
      })

      currentUpperUnit = endCurrentUpperUnit.add(1, upperUnit).startOf(upperUnit)
    }
    return { upperUnits, lowerUnits }
  })

  return {
    timeaxisUnits
  }
}


================================================
FILE: src/playground.ts
================================================
import { createApp } from "vue"
import Playground from "./GanttPlayground.vue"
import ganttastic from "./vue-ganttastic.js"

createApp(Playground).use(ganttastic).mount("#app")


================================================
FILE: src/provider/provideConfig.ts
================================================
import { inject } from "vue"
import { CONFIG_KEY } from "./symbols.js"

export default function provideConfig() {
  const config = inject(CONFIG_KEY)
  if (!config) {
    throw Error("Failed to inject config!")
  }
  return config
}


================================================
FILE: src/provider/provideEmitBarEvent.ts
================================================
import { inject } from "vue"
import { EMIT_BAR_EVENT_KEY } from "./symbols.js"

export default function provideEmitBarEvent() {
  const emitBarEvent = inject(EMIT_BAR_EVENT_KEY)
  if (!emitBarEvent) {
    throw Error("Failed to inject emitBarEvent!")
  }
  return emitBarEvent
}


================================================
FILE: src/provider/provideGetChartRows.ts
================================================
import { inject } from "vue"
import { CHART_ROWS_KEY } from "./symbols.js"

export default function provideGetChartRows() {
  const getChartRows = inject(CHART_ROWS_KEY)
  if (!getChartRows) {
    throw Error("Failed to inject getChartRows!")
  }
  return getChartRows
}


================================================
FILE: src/provider/symbols.ts
================================================
import type { InjectionKey, Ref } from "vue"

import type { GGanttChartConfig } from "../components/GGanttChart.vue"
import type { GanttBarObject } from "../types"

export type ChartRow = { label: string; bars: GanttBarObject[] }
export type GetChartRows = () => ChartRow[]
export type EmitBarEvent = (
  e: MouseEvent,
  bar: GanttBarObject,
  datetime?: string | Date,
  movedBars?: Map<GanttBarObject, { oldStart: string; oldEnd: string }>
) => void

export const CHART_ROWS_KEY = Symbol("CHART_ROWS_KEY") as InjectionKey<GetChartRows>
export const CONFIG_KEY = Symbol("CONFIG_KEY") as InjectionKey<GGanttChartConfig>
export const EMIT_BAR_EVENT_KEY = Symbol("EMIT_BAR_EVENT_KEY") as InjectionKey<EmitBarEvent>
export const BAR_CONTAINER_KEY = Symbol("BAR_CONTAINER_KEY") as InjectionKey<
  Ref<HTMLElement | null>
>


================================================
FILE: src/types.ts
================================================
import type { CSSProperties } from "vue"

export type GanttBarObject = {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [key: string]: any
  ganttBarConfig: {
    id: string
    label?: string
    html?: string
    hasHandles?: boolean
    immobile?: boolean
    bundle?: string
    pushOnOverlap?: boolean
    dragLimitLeft?: number
    dragLimitRight?: number
    style?: CSSProperties
    class?: string
  }
}


================================================
FILE: src/vue-ganttastic.ts
================================================
import type { Plugin } from "vue"
import dayjs from "dayjs"
import isoWeek from "dayjs/plugin/isoWeek"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js"
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js"
import isBetween from "dayjs/plugin/isBetween.js"
import weekOfYear from "dayjs/plugin/weekOfYear"
import advancedFormat from "dayjs/plugin/advancedFormat"
import customParseFormat from "dayjs/plugin/customParseFormat.js"

import type { GanttBarObject } from "./types.js"
import type { ColorScheme } from "./color-schemes"

import GGanttChart from "./components/GGanttChart.vue"
import GGanttRow from "./components/GGanttRow.vue"

export function extendDayjs() {
  dayjs.extend(isSameOrBefore)
  dayjs.extend(isSameOrAfter)
  dayjs.extend(isBetween)
  dayjs.extend(customParseFormat)
  dayjs.extend(weekOfYear)
  dayjs.extend(isoWeek)
  dayjs.extend(advancedFormat)
}

export type { ColorScheme, GanttBarObject }
export { GGanttChart, GGanttRow }

export const ganttastic: Plugin = {
  install(app, options?) {
    extendDayjs()
    app.component("GGanttChart", GGanttChart)
    app.component("GGanttRow", GGanttRow)
  }
}

export default ganttastic


================================================
FILE: tsconfig.config.json
================================================
{
  "extends": "@vue/tsconfig/tsconfig.node.json",
  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
  "compilerOptions": {
    "composite": true,
    "types": ["node"]
  }
}


================================================
FILE: tsconfig.json
================================================
{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "compilerOptions": {
    "baseUrl": "."
  },

  "references": [
    {
      "path": "./tsconfig.config.json"
    }
  ]
}


================================================
FILE: vite.config.mts
================================================
import { fileURLToPath, URL } from "node:url"

import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import postcssPresetEnv from "postcss-preset-env"
import styleInject from "@senojs/rollup-plugin-style-inject"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    styleInject({
      insertAt: "top"
    })
  ],
  css: {
    postcss: {
      plugins: [postcssPresetEnv()]
    }
  },
  build: {
    lib:
      process.env.NODE_ENV === "production"
        ? {
            entry: fileURLToPath(
              new URL("src/vue-ganttastic.ts", import.meta.url)
            ),
            name: "VueGanttastic",
            fileName: "vue-ganttastic"
          }
        : undefined,
    outDir: process.env.NODE_ENV === "production" ? "lib" : "dist",
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into the library
      external: ["vue", "dayjs"],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: "Vue",
          dayjs: "dayjs"
        },
        exports: "named"
      }
    }
  }
})
Download .txt
gitextract_6_i3sk9e/

├── .browserslistrc
├── .editorconfig
├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── deploy.yml
├── .gitignore
├── .prettierrc
├── README.md
├── deploy.sh
├── docs/
│   ├── .vitepress/
│   │   ├── config.ts
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.js
│   ├── GGanttChart.md
│   ├── GGanttRow.md
│   ├── common-use-cases.md
│   ├── examples.md
│   ├── getting-started.md
│   ├── index.md
│   └── introduction.md
├── docs-deploy.yml
├── env.d.ts
├── index.html
├── package.json
├── src/
│   ├── GanttPlayground.vue
│   ├── color-schemes.ts
│   ├── components/
│   │   ├── GGanttBar.vue
│   │   ├── GGanttBarTooltip.vue
│   │   ├── GGanttChart.vue
│   │   ├── GGanttCurrentTime.vue
│   │   ├── GGanttGrid.vue
│   │   ├── GGanttLabelColumn.vue
│   │   ├── GGanttRow.vue
│   │   └── GGanttTimeaxis.vue
│   ├── composables/
│   │   ├── createBarDrag.ts
│   │   ├── useBarDragLimit.ts
│   │   ├── useBarDragManagement.ts
│   │   ├── useDayjsHelper.ts
│   │   ├── useTimePositionMapping.ts
│   │   └── useTimeaxisUnits.ts
│   ├── playground.ts
│   ├── provider/
│   │   ├── provideConfig.ts
│   │   ├── provideEmitBarEvent.ts
│   │   ├── provideGetChartRows.ts
│   │   └── symbols.ts
│   ├── types.ts
│   └── vue-ganttastic.ts
├── tsconfig.config.json
├── tsconfig.json
└── vite.config.mts
Download .txt
SYMBOL INDEX (24 symbols across 14 files)

FILE: docs/.vitepress/theme/index.js
  method enhanceApp (line 7) | enhanceApp(ctx) {

FILE: src/color-schemes.ts
  type Color (line 3) | type Color = CSS.DataType.Color
  type ColorScheme (line 5) | type ColorScheme = {
  type ColorSchemeKey (line 142) | type ColorSchemeKey = keyof typeof colorSchemes

FILE: src/composables/createBarDrag.ts
  function createBarDrag (line 9) | function createBarDrag(

FILE: src/composables/useBarDragLimit.ts
  function useBarDragLimit (line 5) | function useBarDragLimit() {

FILE: src/composables/useBarDragManagement.ts
  function useBarDragManagement (line 9) | function useBarDragManagement() {

FILE: src/composables/useDayjsHelper.ts
  constant DEFAULT_DATE_FORMAT (line 8) | const DEFAULT_DATE_FORMAT = "YYYY-MM-DD HH:mm"
  function useDayjsHelper (line 10) | function useDayjsHelper(config: GGanttChartConfig = provideConfig()) {

FILE: src/composables/useTimePositionMapping.ts
  function useTimePositionMapping (line 7) | function useTimePositionMapping(config: GGanttChartConfig = provideConfi...

FILE: src/composables/useTimeaxisUnits.ts
  function useTimeaxisUnits (line 5) | function useTimeaxisUnits() {

FILE: src/provider/provideConfig.ts
  function provideConfig (line 4) | function provideConfig() {

FILE: src/provider/provideEmitBarEvent.ts
  function provideEmitBarEvent (line 4) | function provideEmitBarEvent() {

FILE: src/provider/provideGetChartRows.ts
  function provideGetChartRows (line 4) | function provideGetChartRows() {

FILE: src/provider/symbols.ts
  type ChartRow (line 6) | type ChartRow = { label: string; bars: GanttBarObject[] }
  type GetChartRows (line 7) | type GetChartRows = () => ChartRow[]
  type EmitBarEvent (line 8) | type EmitBarEvent = (
  constant CHART_ROWS_KEY (line 15) | const CHART_ROWS_KEY = Symbol("CHART_ROWS_KEY") as InjectionKey<GetChart...
  constant CONFIG_KEY (line 16) | const CONFIG_KEY = Symbol("CONFIG_KEY") as InjectionKey<GGanttChartConfig>
  constant EMIT_BAR_EVENT_KEY (line 17) | const EMIT_BAR_EVENT_KEY = Symbol("EMIT_BAR_EVENT_KEY") as InjectionKey<...
  constant BAR_CONTAINER_KEY (line 18) | const BAR_CONTAINER_KEY = Symbol("BAR_CONTAINER_KEY") as InjectionKey<

FILE: src/types.ts
  type GanttBarObject (line 3) | type GanttBarObject = {

FILE: src/vue-ganttastic.ts
  function extendDayjs (line 17) | function extendDayjs() {
  method install (line 31) | install(app, options?) {
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (109K chars).
[
  {
    "path": ".browserslistrc",
    "chars": 30,
    "preview": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": ".editorconfig",
    "chars": 142,
    "preview": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true"
  },
  {
    "path": ".eslintrc.cjs",
    "chars": 706,
    "preview": "/* eslint-env node */\nrequire(\"@rushstack/eslint-patch/modern-module-resolution\")\n\nmodule.exports = {\n  root: true,\n\n  e"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 723,
    "preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 1997,
    "preview": "# Sample workflow for building and deploying a VitePress site to GitHub Pages\n#\nname: Deploy VitePress site to Pages\n\non"
  },
  {
    "path": ".gitignore",
    "chars": 335,
    "preview": ".DS_Store\nnode_modules\n/lib\n/lib_types\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debu"
  },
  {
    "path": ".prettierrc",
    "chars": 69,
    "preview": "{\n  \"semi\": false,\n  \"trailingComma\": \"none\",\n  \"endOfLine\": \"auto\"\n}"
  },
  {
    "path": "README.md",
    "chars": 4592,
    "preview": "# Vue Ganttastic\n\n<div style=\"display: flex; flex-direction: column; align-items:center;\">\n<img\n    src=\"https://user-im"
  },
  {
    "path": "deploy.sh",
    "chars": 173,
    "preview": "set -e\n\nnpm run docs:build\ncd docs/.vuepress/dist\n\ngit init\ngit add -A\ngit commit -m 'deploy'\n\ngit push -f git@github.co"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "chars": 1137,
    "preview": "import { defineConfig } from 'vitepress'\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n "
  },
  {
    "path": "docs/.vitepress/theme/custom.css",
    "chars": 152,
    "preview": ":root {\n    --vp-home-hero-name-color: #2fb585;\n    --vp-button-brand-bg: #2fb585;\n    --vp-button-brand-hover-bg: #354b"
  },
  {
    "path": "docs/.vitepress/theme/index.js",
    "chars": 227,
    "preview": "import DefaultTheme from 'vitepress/theme'\nimport './custom.css'\nimport {ganttastic} from \"../../../src/vue-ganttastic\"\n"
  },
  {
    "path": "docs/GGanttChart.md",
    "chars": 5279,
    "preview": "# API: GGanttChart\nThe main component of Vue Ganttastic. Represents an entire chart and is meant to have at least one `g"
  },
  {
    "path": "docs/GGanttRow.md",
    "chars": 1601,
    "preview": "# API: GGanttRow\nRepresents a single row of the chart. It is meant to be a child component of `g-gantt-chart`.  \n\n## Pro"
  },
  {
    "path": "docs/common-use-cases.md",
    "chars": 5747,
    "preview": "# Common use cases\n The following section provides a non-exhausting list of common use cases and special features of Vue"
  },
  {
    "path": "docs/examples.md",
    "chars": 5708,
    "preview": "\n# Live Demos\n\n## Simple hour chart  \n- `precision`: `hour`\n<g-gantt-chart chart-start=\"01.01.2022 12:00\" chart-end=\"02."
  },
  {
    "path": "docs/getting-started.md",
    "chars": 2736,
    "preview": "# Getting started\n\n## Install\n\nYou can add Vue Ganttastic to your project using <kbd>npm</kbd>:\n\n```\nnpm install @infect"
  },
  {
    "path": "docs/index.md",
    "chars": 879,
    "preview": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n  name: Vue-Ganttastic\n  text: Gantt c"
  },
  {
    "path": "docs/introduction.md",
    "chars": 1416,
    "preview": "# Introduction\nVue Ganttastic is a simple, interactive and highly customizable Gantt chart component for Vue 3.   \n\n## F"
  },
  {
    "path": "docs-deploy.yml",
    "chars": 1888,
    "preview": "# Sample workflow for building and deploying a VitePress site to GitHub Pages\n#\nname: Deploy VitePress site to Pages\n\non"
  },
  {
    "path": "env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "index.html",
    "chars": 598,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\">\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\t"
  },
  {
    "path": "package.json",
    "chars": 2186,
    "preview": "{\n  \"name\": \"@infectoone/vue-ganttastic\",\n  \"version\": \"2.3.2\",\n  \"description\": \"A simple and customizable Gantt chart "
  },
  {
    "path": "src/GanttPlayground.vue",
    "chars": 6198,
    "preview": "<template>\n  <g-gantt-chart\n    :chart-start=\"chartStart\"\n    :chart-end=\"chartEnd\"\n    precision=\"week\"\n    :row-height"
  },
  {
    "path": "src/color-schemes.ts",
    "chars": 3129,
    "preview": "import type * as CSS from \"csstype\"\n\ntype Color = CSS.DataType.Color\n\nexport type ColorScheme = {\n  primary: Color\n  sec"
  },
  {
    "path": "src/components/GGanttBar.vue",
    "chars": 4167,
    "preview": "<template>\n  <div\n    :id=\"barConfig.id\"\n    :class=\"['g-gantt-bar', barConfig.class || '']\"\n    :style=\"{\n      ...barC"
  },
  {
    "path": "src/components/GGanttBarTooltip.vue",
    "chars": 3079,
    "preview": "<template>\n  <teleport to=\"body\">\n    <transition name=\"g-fade\" mode=\"out-in\">\n      <div\n        v-if=\"modelValue\"\n    "
  },
  {
    "path": "src/components/GGanttChart.vue",
    "chars": 8131,
    "preview": "<template>\n  <div>\n    <div :class=\"[{ 'labels-in-column': !!labelColumnTitle }]\">\n      <g-gantt-label-column\n        v"
  },
  {
    "path": "src/components/GGanttCurrentTime.vue",
    "chars": 1283,
    "preview": "<template>\n  <div\n    class=\"g-grid-current-time\"\n    :style=\"{\n      left: `${xDist}px`\n    }\"\n  >\n    <div\n      class"
  },
  {
    "path": "src/components/GGanttGrid.vue",
    "chars": 868,
    "preview": "<template>\n  <div class=\"g-grid-container\">\n    <div\n      v-for=\"{ label, value, width } in timeaxisUnits.lowerUnits\"\n "
  },
  {
    "path": "src/components/GGanttLabelColumn.vue",
    "chars": 1839,
    "preview": "<template>\n  <div class=\"g-label-column\" :style=\"{ fontFamily: font, color: colors.text }\">\n    <slot name=\"label-column"
  },
  {
    "path": "src/components/GGanttRow.vue",
    "chars": 3104,
    "preview": "<template>\n  <div\n    class=\"g-gantt-row\"\n    :style=\"rowStyle\"\n    @dragover.prevent=\"isHovering = true\"\n    @dragleave"
  },
  {
    "path": "src/components/GGanttTimeaxis.vue",
    "chars": 2080,
    "preview": "<template>\n  <div class=\"g-timeaxis\">\n    <div class=\"g-timeunits-container\">\n      <div\n        v-for=\"({ label, value,"
  },
  {
    "path": "src/composables/createBarDrag.ts",
    "chars": 3868,
    "preview": "import { ref } from \"vue\"\nimport type { GGanttChartConfig } from \"../components/GGanttChart.vue\"\nimport provideConfig fr"
  },
  {
    "path": "src/composables/useBarDragLimit.ts",
    "chars": 6827,
    "preview": "import type { GanttBarObject } from \"../types\"\nimport provideConfig from \"../provider/provideConfig.js\"\nimport provideGe"
  },
  {
    "path": "src/composables/useBarDragManagement.ts",
    "chars": 6818,
    "preview": "import type { GanttBarObject } from \"../types\"\n\nimport createBarDrag from \"./createBarDrag.js\"\nimport useDayjsHelper fro"
  },
  {
    "path": "src/composables/useDayjsHelper.ts",
    "chars": 1522,
    "preview": "import dayjs, { type Dayjs } from \"dayjs\"\nimport { computed } from \"vue\"\n\nimport type { GGanttChartConfig } from \"../com"
  },
  {
    "path": "src/composables/useTimePositionMapping.ts",
    "chars": 1125,
    "preview": "import type { GGanttChartConfig } from \"../components/GGanttChart.vue\"\nimport { computed } from \"vue\"\n\nimport useDayjsHe"
  },
  {
    "path": "src/composables/useTimeaxisUnits.ts",
    "chars": 3196,
    "preview": "import { computed } from \"vue\"\nimport useDayjsHelper from \"./useDayjsHelper.js\"\nimport provideConfig from \"../provider/p"
  },
  {
    "path": "src/playground.ts",
    "chars": 177,
    "preview": "import { createApp } from \"vue\"\nimport Playground from \"./GanttPlayground.vue\"\nimport ganttastic from \"./vue-ganttastic."
  },
  {
    "path": "src/provider/provideConfig.ts",
    "chars": 233,
    "preview": "import { inject } from \"vue\"\nimport { CONFIG_KEY } from \"./symbols.js\"\n\nexport default function provideConfig() {\n  cons"
  },
  {
    "path": "src/provider/provideEmitBarEvent.ts",
    "chars": 279,
    "preview": "import { inject } from \"vue\"\nimport { EMIT_BAR_EVENT_KEY } from \"./symbols.js\"\n\nexport default function provideEmitBarEv"
  },
  {
    "path": "src/provider/provideGetChartRows.ts",
    "chars": 271,
    "preview": "import { inject } from \"vue\"\nimport { CHART_ROWS_KEY } from \"./symbols.js\"\n\nexport default function provideGetChartRows("
  },
  {
    "path": "src/provider/symbols.ts",
    "chars": 820,
    "preview": "import type { InjectionKey, Ref } from \"vue\"\n\nimport type { GGanttChartConfig } from \"../components/GGanttChart.vue\"\nimp"
  },
  {
    "path": "src/types.ts",
    "chars": 433,
    "preview": "import type { CSSProperties } from \"vue\"\n\nexport type GanttBarObject = {\n  // eslint-disable-next-line @typescript-eslin"
  },
  {
    "path": "src/vue-ganttastic.ts",
    "chars": 1171,
    "preview": "import type { Plugin } from \"vue\"\nimport dayjs from \"dayjs\"\nimport isoWeek from \"dayjs/plugin/isoWeek\"\nimport isSameOrBe"
  },
  {
    "path": "tsconfig.config.json",
    "chars": 196,
    "preview": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.node.json\",\n  \"include\": [\"vite.config.*\", \"vitest.config.*\", \"cypress.config.*\"]"
  },
  {
    "path": "tsconfig.json",
    "chars": 228,
    "preview": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.web.json\",\n  \"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n  \"compilerOpti"
  },
  {
    "path": "vite.config.mts",
    "chars": 1181,
    "preview": "import { fileURLToPath, URL } from \"node:url\"\n\nimport { defineConfig } from \"vite\"\nimport vue from \"@vitejs/plugin-vue\"\n"
  }
]

About this extraction

This page contains the full source code of the InfectoOne/vue-ganttastic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (98.2 KB), approximately 29.3k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!