vue-gantt-absolute 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vue-gantt-absolute contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # vue-gantt-absolute
2
+
3
+ Librería de diagrama de Gantt libre (MIT) para Vue 3 composition API
4
+
5
+ Estado: **MVP v0.2** — pensada para crecer con la comunidad.
6
+
7
+ ## Instalación
8
+
9
+ ```bash
10
+ npm install vue-gantt-absolute
11
+ ```
12
+
13
+ ## Uso
14
+
15
+ ```vue
16
+ <script setup lang="ts">
17
+ import { ref } from 'vue'
18
+ import { GanttChart, type GanttTask } from 'vue-gantt-absolute'
19
+ import 'vue-gantt-absolute/style.css'
20
+
21
+ const tasks = ref<GanttTask[]>([
22
+ { id: 1, name: 'Investigación', start: '2026-07-01', end: '2026-07-04' },
23
+ { id: 2, name: 'Diseño', start: '2026-07-03', end: '2026-07-08' }
24
+ ])
25
+ </script>
26
+
27
+ <template>
28
+ <GanttChart :tasks="tasks" @update:tasks="tasks = $event" />
29
+ </template>
30
+ ```
31
+
32
+ ## Props
33
+
34
+ | Prop | Tipo | Default | Descripción |
35
+ |-------------------------|-------------------------------|----------|-------------------------------------------|
36
+ | `tasks` | `GanttTask[]` | — (requerido) | Lista de tareas (ver campos abajo) |
37
+ | `dayWidth` | `number` | `44` | Ancho en px de cada columna de día |
38
+ | `rowHeight` | `number` | `40` | Alto en px de cada fila |
39
+ | `leftWidth` | `number` | `400` | Ancho inicial del panel izquierdo (redimensionable arrastrando el borde, y colapsable con el botón `‹`/`›`) |
40
+ | `startDateColumnWidth` | `number` | `110` | Ancho de la columna "Start Date" |
41
+ | `progressColumnWidth` | `number` | `90` | Ancho de la columna "Progress" |
42
+ | `durationColumnWidth` | `number` | `90` | Ancho de la columna "Duration" |
43
+ | `height` | `string` | `'auto'` | Alto total visible del componente. `'auto'` ajusta al contenido; pasa un valor fijo (ej. `'400px'`) para forzar scroll interno |
44
+ | `holidays` | `string[]` | `[]` | Fechas (`YYYY-MM-DD`) a sombrear como feriado, además de sábados/domingos |
45
+ | `labels` | `GanttLabels` | `{}` | Textos de los headers del panel izquierdo, para i18n: `{ taskName?, startDate?, progress?, duration? }` |
46
+ | `showPlanned` | `boolean` | `true` | Muestra/oculta la franja de baseline (`plannedStart`/`plannedEnd`) y la línea "Planned" del tooltip |
47
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | `'auto'` respeta `prefers-color-scheme` del sistema; `'light'`/`'dark'` fuerza el tema |
48
+
49
+ ### Campos de `GanttTask`
50
+
51
+ | Campo | Tipo | Descripción |
52
+ |-----------------|--------------------------|----------------------------------------------------|
53
+ | `id` | `string \| number` | Identificador único |
54
+ | `name` | `string` | Nombre mostrado en el panel izquierdo y la barra |
55
+ | `start` | `string` (`YYYY-MM-DD`) | Fecha de inicio |
56
+ | `end` | `string` (`YYYY-MM-DD`) | Fecha de fin (igual a `start` para un milestone) |
57
+ | `dependsOn?` | `(string \| number)[]` | Ids de tareas predecesoras (dibuja flechas) |
58
+ | `parentId?` | `string \| number` | Id de la tarea padre (jerarquía colapsable) |
59
+ | `progress?` | `number` (0–100) | Porcentaje de avance mostrado dentro de la barra y de la columna "Progress" |
60
+ | `milestone?` | `boolean` | Si es `true`, se dibuja como rombo en vez de barra |
61
+ | `plannedStart?` | `string` (`YYYY-MM-DD`) | Fecha de inicio planificada (baseline); requiere `showPlanned` |
62
+ | `plannedEnd?` | `string` (`YYYY-MM-DD`) | Fecha de fin planificada (baseline); requiere `showPlanned` |
63
+ | `color?` | `string` (CSS color) | Color de la barra/rombo de esta tarea; si se omite usa el acento por defecto (`--vga-accent`) |
64
+
65
+ ## Eventos
66
+
67
+ - `update:tasks` — se emite al soltar una barra arrastrada o redimensionada, con las tareas actualizadas.
68
+ - `update:leftWidth` — se emite al soltar el handle de resize del panel izquierdo, con el nuevo ancho en px (útil para `v-model:left-width`).
69
+
70
+ ## Qué incluye este MVP
71
+
72
+ - Grilla de días calculada automáticamente según el rango de tareas, con fila de meses arriba
73
+ - Barras posicionadas por fecha, con color personalizable por tarea (`color`)
74
+ - Arrastre horizontal de barras (mover fechas) con `PointerEvent` + `setPointerCapture`
75
+ - Redimensionar barras arrastrando el borde izquierdo o derecho (cambia `start`/`end` respetando 1 día mínimo)
76
+ - Scroll vertical sincronizado entre lista de tareas y timeline
77
+ - Flechas de dependencia entre tareas (`dependsOn`), con overlay SVG que se recalcula al arrastrar o redimensionar
78
+ - Jerarquía padre/hijo colapsable (`parentId`) con indentado y toggle ▼/▶
79
+ - Línea vertical marcando el día de hoy
80
+ - Sombreado de fines de semana y feriados (`holidays`)
81
+ - Panel izquierdo con columnas Task Name / Start Date / Progress / Duration, redimensionable (drag) y colapsable
82
+ - Barra de progreso interna (`progress`), también visible como relleno en la columna "Progress"
83
+ - Baseline: franja de "planificado vs. actual" (`plannedStart`/`plannedEnd`), togglable con `showPlanned`
84
+ - Hitos/milestones (`milestone: true`) dibujados como rombo, arrastrables
85
+ - Tooltip al pasar el mouse por una barra o hito, con fechas actuales y planificadas
86
+ - Dark mode automático (`prefers-color-scheme`) o forzado vía prop `theme`
87
+ - Labels configurables (i18n) vía prop `labels`
88
+
89
+ ## Qué falta (próximas versiones)
90
+
91
+ - Niveles de zoom (semana/mes)
92
+ - Edición inline del nombre de tarea
93
+ - Auto-cálculo de fechas del padre en base a sus hijos (roll-up)
94
+
95
+ ## Desarrollo local
96
+
97
+ ```bash
98
+ npm install
99
+ npm run dev # levanta el playground en http://localhost:5173
100
+ npm run build # genera dist/ como librería (ESM + UMD + tipos)
101
+ ```
102
+
103
+ ## Licencia
104
+
105
+ MIT — libre para usar, modificar y distribuir. Si te resulta útil, las donaciones son bienvenidas: [paypal.me/cristianabsoluto](https://paypal.me/cristianabsoluto).
@@ -0,0 +1,35 @@
1
+ import type { GanttTask, GanttLabels } from '../types';
2
+ type __VLS_Props = {
3
+ tasks: GanttTask[];
4
+ dayWidth?: number;
5
+ rowHeight?: number;
6
+ leftWidth?: number;
7
+ startDateColumnWidth?: number;
8
+ progressColumnWidth?: number;
9
+ durationColumnWidth?: number;
10
+ height?: string;
11
+ holidays?: string[];
12
+ labels?: GanttLabels;
13
+ showPlanned?: boolean;
14
+ theme?: 'light' | 'dark' | 'auto';
15
+ };
16
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
+ "update:tasks": (tasks: GanttTask[]) => any;
18
+ "update:leftWidth": (width: number) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ "onUpdate:tasks"?: ((tasks: GanttTask[]) => any) | undefined;
21
+ "onUpdate:leftWidth"?: ((width: number) => any) | undefined;
22
+ }>, {
23
+ dayWidth: number;
24
+ rowHeight: number;
25
+ leftWidth: number;
26
+ startDateColumnWidth: number;
27
+ progressColumnWidth: number;
28
+ durationColumnWidth: number;
29
+ height: string;
30
+ holidays: string[];
31
+ labels: GanttLabels;
32
+ showPlanned: boolean;
33
+ theme: "light" | "dark" | "auto";
34
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
+ export default _default;
@@ -0,0 +1,34 @@
1
+ import { type Ref } from 'vue';
2
+ import type { GanttTask } from '../types';
3
+ declare function toDate(value: string): Date;
4
+ declare function diffDays(a: Date, b: Date): number;
5
+ export declare function useGanttLayout(tasks: Ref<GanttTask[]>, dayWidth: Ref<number>, rowHeight: Ref<number>): {
6
+ rangeStart: import("vue").ComputedRef<Date>;
7
+ rangeEnd: import("vue").ComputedRef<Date>;
8
+ totalDays: import("vue").ComputedRef<number>;
9
+ days: import("vue").ComputedRef<Date[]>;
10
+ totalWidth: import("vue").ComputedRef<number>;
11
+ barStyle: (task: GanttTask) => {
12
+ left: string;
13
+ width: string;
14
+ };
15
+ barRect: (task: GanttTask) => {
16
+ left: number;
17
+ width: number;
18
+ };
19
+ plannedBarStyle: (task: GanttTask) => {
20
+ left: string;
21
+ width: string;
22
+ } | null;
23
+ plannedBarRect: (task: GanttTask) => {
24
+ left: number;
25
+ width: number;
26
+ } | null;
27
+ diffDays: typeof diffDays;
28
+ toDate: typeof toDate;
29
+ offsetToDate: (offsetDays: number) => Date;
30
+ addDays: (date: Date, days: number) => Date;
31
+ formatISO: (date: Date) => string;
32
+ rowHeight: Ref<number, number>;
33
+ };
34
+ export {};
@@ -0,0 +1,3 @@
1
+ import GanttChart from './components/GanttChart.vue';
2
+ export { GanttChart };
3
+ export type { GanttTask, GanttOptions, GanttLabels } from './types';
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .vga-root[data-v-02881458]{--vga-bg: #ffffff;--vga-border: #d0d5dd;--vga-text: #1d2939;--vga-muted-text: #475467;--vga-muted-text-2: #667085;--vga-header-bg: #f9fafb;--vga-row-border: #eaecf0;--vga-shade-bg: #f2f4f7;--vga-accent: #7f56d9;--vga-accent-soft: rgba(127, 86, 217, .2);--vga-progress-overlay: rgba(0, 0, 0, .22);--vga-edge: #98a2b3;--vga-milestone: #f79009;--vga-today: #f04438;--vga-planned-stripe-1: #d0d5dd;--vga-planned-stripe-2: #e4e7ec;--vga-planned-border: #98a2b3;--vga-tooltip-bg: #101828;--vga-tooltip-text: #ffffff;--vga-tooltip-muted: #d0d5dd;--vga-btn-bg: #ffffff;--vga-btn-hover-bg: #f2f4f7;display:flex;border:1px solid var(--vga-border);font-family:system-ui,sans-serif;font-size:13px;color:var(--vga-text);background:var(--vga-bg);overflow:hidden;position:relative}@media (prefers-color-scheme: dark){.vga-root[data-v-02881458]:not([data-vga-theme=light]){--vga-bg: #0d1117;--vga-border: #30363d;--vga-text: #e6edf3;--vga-muted-text: #9aa4b2;--vga-muted-text-2: #8b949e;--vga-header-bg: #161b22;--vga-row-border: #21262d;--vga-shade-bg: #1c2128;--vga-accent: #9b8afb;--vga-accent-soft: rgba(155, 138, 251, .28);--vga-progress-overlay: rgba(0, 0, 0, .35);--vga-edge: #6e7681;--vga-today: #ff6a5c;--vga-planned-stripe-1: #30363d;--vga-planned-stripe-2: #3d444d;--vga-planned-border: #6e7681;--vga-tooltip-bg: #1c2128;--vga-tooltip-text: #e6edf3;--vga-tooltip-muted: #8b949e;--vga-btn-bg: #161b22;--vga-btn-hover-bg: #21262d}}.vga-root[data-vga-theme=dark][data-v-02881458]{--vga-bg: #0d1117;--vga-border: #30363d;--vga-text: #e6edf3;--vga-muted-text: #9aa4b2;--vga-muted-text-2: #8b949e;--vga-header-bg: #161b22;--vga-row-border: #21262d;--vga-shade-bg: #1c2128;--vga-accent: #9b8afb;--vga-accent-soft: rgba(155, 138, 251, .28);--vga-progress-overlay: rgba(0, 0, 0, .35);--vga-edge: #6e7681;--vga-today: #ff6a5c;--vga-planned-stripe-1: #30363d;--vga-planned-stripe-2: #3d444d;--vga-planned-border: #6e7681;--vga-tooltip-bg: #1c2128;--vga-tooltip-text: #e6edf3;--vga-tooltip-muted: #8b949e;--vga-btn-bg: #161b22;--vga-btn-hover-bg: #21262d}.vga-left[data-v-02881458]{flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--vga-border);overflow:hidden;transition:width .15s ease}.vga-left-header[data-v-02881458]{display:flex;align-items:stretch;font-weight:600;background:var(--vga-header-bg);border-bottom:1px solid var(--vga-border);flex-shrink:0}.vga-left-body[data-v-02881458]{overflow-y:hidden;flex:1}.vga-left-row[data-v-02881458]{box-sizing:border-box;display:flex;align-items:stretch;border-bottom:1px solid var(--vga-row-border);white-space:nowrap}.vga-left-col[data-v-02881458]{box-sizing:border-box;flex-shrink:0;display:flex;align-items:center;gap:4px;padding:0 8px;overflow:hidden}.vga-col-name[data-v-02881458]{overflow:hidden}.vga-name-text[data-v-02881458]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.vga-col-date[data-v-02881458],.vga-col-progress[data-v-02881458],.vga-col-duration[data-v-02881458]{justify-content:center;border-left:1px solid var(--vga-row-border);color:var(--vga-muted-text)}.vga-col-progress[data-v-02881458]{position:relative}.vga-progress-fill[data-v-02881458]{position:absolute;top:4px;bottom:4px;left:3px;right:3px;background:var(--vga-accent-soft);border-radius:3px;pointer-events:none;transition:width .15s ease}.vga-progress-text[data-v-02881458]{position:relative;z-index:1}.vga-toggle[data-v-02881458]{display:inline-flex;width:12px;flex-shrink:0;cursor:pointer;font-size:9px;color:var(--vga-muted-text-2);-webkit-user-select:none;user-select:none}.vga-toggle-spacer[data-v-02881458]{display:inline-flex;width:12px;flex-shrink:0}.vga-resize-left[data-v-02881458]{position:absolute;top:0;bottom:0;width:6px;transform:translate(-50%);z-index:3;cursor:col-resize;touch-action:none}.vga-resize-left[data-v-02881458]:hover{background:var(--vga-accent-soft)}.vga-collapse-toggle[data-v-02881458]{position:absolute;top:8px;transform:translate(-50%);z-index:4;width:18px;height:18px;display:flex;align-items:center;justify-content:center;padding:0;border:1px solid var(--vga-border);border-radius:50%;background:var(--vga-btn-bg);color:var(--vga-muted-text);font-size:11px;line-height:1;cursor:pointer}.vga-collapse-toggle[data-v-02881458]:hover{background:var(--vga-btn-hover-bg)}.vga-right[data-v-02881458]{flex:1;overflow:auto;position:relative}.vga-right-header[data-v-02881458]{position:sticky;top:0;z-index:3;display:flex;flex-direction:column;background:var(--vga-header-bg);border-bottom:1px solid var(--vga-border)}.vga-month-row[data-v-02881458]{display:flex;border-bottom:1px solid var(--vga-row-border)}.vga-month-cell[data-v-02881458]{box-sizing:border-box;flex-shrink:0;display:flex;align-items:center;padding-left:8px;font-weight:600;color:var(--vga-text);border-right:1px solid var(--vga-row-border)}.vga-day-row[data-v-02881458]{display:flex}.vga-day-cell[data-v-02881458]{box-sizing:border-box;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-right:1px solid var(--vga-row-border);color:var(--vga-muted-text-2)}.vga-day-cell--shaded[data-v-02881458]{background:var(--vga-shade-bg)}.vga-rows[data-v-02881458]{position:relative}.vga-row[data-v-02881458]{box-sizing:border-box;position:relative;border-bottom:1px solid var(--vga-row-border)}.vga-weekend[data-v-02881458]{position:absolute;top:0;background:var(--vga-header-bg);pointer-events:none}.vga-today-line[data-v-02881458]{position:absolute;top:0;width:2px;background:var(--vga-today);pointer-events:none;z-index:1}.vga-edges[data-v-02881458]{position:absolute;top:0;left:0;pointer-events:none}.vga-edge-path[data-v-02881458]{fill:none;stroke:var(--vga-edge);stroke-width:1.5}.vga-bar-planned[data-v-02881458]{position:absolute;bottom:4px;height:5px;border-radius:3px;background:repeating-linear-gradient(45deg,var(--vga-planned-stripe-1),var(--vga-planned-stripe-1) 4px,var(--vga-planned-stripe-2) 4px,var(--vga-planned-stripe-2) 8px);border:1px solid var(--vga-planned-border);box-sizing:border-box;pointer-events:none}.vga-bar[data-v-02881458]{position:absolute;top:4px;bottom:12px;display:flex;align-items:center;padding:0 8px;background:var(--vga-accent);color:#fff;border-radius:4px;cursor:grab;white-space:nowrap;overflow:hidden;-webkit-user-select:none;user-select:none;touch-action:none}.vga-bar[data-v-02881458]:active{cursor:grabbing}.vga-bar-progress[data-v-02881458]{position:absolute;top:0;left:0;bottom:0;background:var(--vga-progress-overlay);pointer-events:none}.vga-bar-label[data-v-02881458]{position:relative;z-index:1}.vga-resize-handle[data-v-02881458]{position:absolute;top:0;bottom:0;width:6px;cursor:ew-resize;touch-action:none}.vga-resize-handle--left[data-v-02881458]{left:0}.vga-resize-handle--right[data-v-02881458]{right:0}.vga-milestone[data-v-02881458]{position:absolute;top:50%;width:14px;height:14px;background:var(--vga-milestone);transform:translate(-50%,-50%) rotate(45deg);cursor:grab;touch-action:none}.vga-milestone[data-v-02881458]:active{cursor:grabbing}.vga-tooltip[data-v-02881458]{position:fixed;transform:translateY(-100%);background:var(--vga-tooltip-bg);color:var(--vga-tooltip-text);padding:6px 10px;border-radius:6px;font-size:12px;line-height:1.4;pointer-events:none;white-space:nowrap;z-index:10;box-shadow:0 4px 12px #0003}.vga-tooltip-planned[data-v-02881458]{color:var(--vga-tooltip-muted)}
@@ -0,0 +1,23 @@
1
+ export interface GanttTask {
2
+ id: string | number;
3
+ name: string;
4
+ start: string;
5
+ end: string;
6
+ dependsOn?: (string | number)[];
7
+ parentId?: string | number;
8
+ progress?: number;
9
+ milestone?: boolean;
10
+ plannedStart?: string;
11
+ plannedEnd?: string;
12
+ color?: string;
13
+ }
14
+ export interface GanttOptions {
15
+ dayWidth?: number;
16
+ rowHeight?: number;
17
+ }
18
+ export interface GanttLabels {
19
+ taskName?: string;
20
+ startDate?: string;
21
+ progress?: string;
22
+ duration?: string;
23
+ }
@@ -0,0 +1,536 @@
1
+ import { computed as g, defineComponent as At, toRefs as Ut, ref as E, reactive as Gt, watch as kt, openBlock as u, createElementBlock as d, normalizeStyle as s, createElementVNode as o, toDisplayString as v, Fragment as O, renderList as A, unref as C, createCommentVNode as V, normalizeClass as Nt, withModifiers as $t } from "vue";
2
+ const Q = 1e3 * 60 * 60 * 24;
3
+ function $(i) {
4
+ const h = new Date(i);
5
+ return h.setHours(0, 0, 0, 0), h;
6
+ }
7
+ function U(i, h) {
8
+ return Math.round((h.getTime() - i.getTime()) / Q);
9
+ }
10
+ function Ft(i, h, T) {
11
+ const w = g(() => {
12
+ if (i.value.length === 0) return $((/* @__PURE__ */ new Date()).toISOString());
13
+ const r = i.value.flatMap((l) => [
14
+ $(l.start).getTime(),
15
+ ...l.plannedStart ? [$(l.plannedStart).getTime()] : []
16
+ ]);
17
+ return new Date(Math.min(...r));
18
+ }), c = g(() => {
19
+ if (i.value.length === 0) return $((/* @__PURE__ */ new Date()).toISOString());
20
+ const r = i.value.flatMap((l) => [
21
+ $(l.end).getTime(),
22
+ ...l.plannedEnd ? [$(l.plannedEnd).getTime()] : []
23
+ ]);
24
+ return new Date(Math.max(...r));
25
+ }), X = g(() => Math.max(U(w.value, c.value) + 1, 1)), m = g(() => {
26
+ const r = [];
27
+ for (let l = 0; l < X.value; l++)
28
+ r.push(new Date(w.value.getTime() + l * Q));
29
+ return r;
30
+ }), x = g(() => X.value * h.value);
31
+ function _(r) {
32
+ const l = $(r.start), k = $(r.end), M = U(w.value, l), I = U(l, k) + 1;
33
+ return {
34
+ left: M * h.value,
35
+ width: I * h.value
36
+ };
37
+ }
38
+ function L(r) {
39
+ const { left: l, width: k } = _(r);
40
+ return {
41
+ left: `${l}px`,
42
+ width: `${k}px`
43
+ };
44
+ }
45
+ function H(r) {
46
+ if (!r.plannedStart || !r.plannedEnd) return null;
47
+ const l = $(r.plannedStart), k = $(r.plannedEnd), M = U(w.value, l), I = U(l, k) + 1;
48
+ return {
49
+ left: M * h.value,
50
+ width: I * h.value
51
+ };
52
+ }
53
+ function Z(r) {
54
+ const l = H(r);
55
+ return l ? {
56
+ left: `${l.left}px`,
57
+ width: `${l.width}px`
58
+ } : null;
59
+ }
60
+ function j(r) {
61
+ return new Date(w.value.getTime() + r * Q);
62
+ }
63
+ function P(r, l) {
64
+ return new Date(r.getTime() + l * Q);
65
+ }
66
+ function G(r) {
67
+ return r.toISOString().slice(0, 10);
68
+ }
69
+ return {
70
+ rangeStart: w,
71
+ rangeEnd: c,
72
+ totalDays: X,
73
+ days: m,
74
+ totalWidth: x,
75
+ barStyle: L,
76
+ barRect: _,
77
+ plannedBarStyle: Z,
78
+ plannedBarRect: H,
79
+ diffDays: U,
80
+ toDate: $,
81
+ offsetToDate: j,
82
+ addDays: P,
83
+ formatISO: G,
84
+ rowHeight: T
85
+ };
86
+ }
87
+ const Yt = ["data-vga-theme"], Vt = ["onClick"], jt = {
88
+ key: 1,
89
+ class: "vga-toggle-spacer"
90
+ }, qt = { class: "vga-name-text" }, Jt = { class: "vga-progress-text" }, Kt = ["aria-label"], Qt = ["width", "height"], Zt = ["d"], te = ["onPointerdown", "onMouseenter"], ee = ["onPointerdown", "onMouseenter"], ne = { class: "vga-bar-label" }, ae = ["onPointerdown"], le = ["onPointerdown"], se = {
91
+ key: 0,
92
+ class: "vga-tooltip-planned"
93
+ }, wt = 24, xt = 32, oe = 4, re = /* @__PURE__ */ At({
94
+ __name: "GanttChart",
95
+ props: {
96
+ tasks: {},
97
+ dayWidth: { default: 44 },
98
+ rowHeight: { default: 40 },
99
+ leftWidth: { default: 400 },
100
+ startDateColumnWidth: { default: 110 },
101
+ progressColumnWidth: { default: 90 },
102
+ durationColumnWidth: { default: 90 },
103
+ height: { default: "auto" },
104
+ holidays: { default: () => [] },
105
+ labels: { default: () => ({}) },
106
+ showPlanned: { type: Boolean, default: !0 },
107
+ theme: { default: "auto" }
108
+ },
109
+ emits: ["update:tasks", "update:leftWidth"],
110
+ setup(i, { emit: h }) {
111
+ const T = wt + xt, w = {
112
+ taskName: "Task Name",
113
+ startDate: "Start Date",
114
+ progress: "Progress",
115
+ duration: "Duration"
116
+ }, c = i, X = h, { dayWidth: m, rowHeight: x } = Ut(c), _ = g(() => ({
117
+ ...w,
118
+ ...c.labels
119
+ })), L = E(c.leftWidth), H = E(!1), Z = g(
120
+ () => c.startDateColumnWidth + c.progressColumnWidth + c.durationColumnWidth + 60
121
+ ), j = g(
122
+ () => Math.max(
123
+ L.value - c.startDateColumnWidth - c.progressColumnWidth - c.durationColumnWidth,
124
+ 60
125
+ )
126
+ ), P = E(!1), G = g(() => P.value ? 0 : L.value);
127
+ function r(t) {
128
+ const n = t.currentTarget;
129
+ n.setPointerCapture(t.pointerId);
130
+ const e = t.clientX, a = L.value;
131
+ H.value = !0;
132
+ function f(p) {
133
+ const D = p.clientX - e;
134
+ L.value = Math.max(a + D, Z.value);
135
+ }
136
+ function y(p) {
137
+ n.releasePointerCapture(p.pointerId), n.removeEventListener("pointermove", f), n.removeEventListener("pointerup", y), H.value = !1, X("update:leftWidth", L.value);
138
+ }
139
+ n.addEventListener("pointermove", f), n.addEventListener("pointerup", y);
140
+ }
141
+ const l = E(c.tasks.map((t) => ({ ...t }))), k = E(!1), M = Gt(/* @__PURE__ */ new Set()), {
142
+ days: I,
143
+ totalWidth: tt,
144
+ barStyle: St,
145
+ barRect: et,
146
+ plannedBarStyle: st,
147
+ diffDays: ot,
148
+ toDate: W,
149
+ addDays: q,
150
+ formatISO: N,
151
+ rangeStart: Dt,
152
+ totalDays: Ct
153
+ } = Ft(l, m, x);
154
+ function rt() {
155
+ X(
156
+ "update:tasks",
157
+ l.value.map((t) => ({ ...t }))
158
+ );
159
+ }
160
+ const it = g(() => {
161
+ const t = /* @__PURE__ */ new Map();
162
+ for (const n of l.value) {
163
+ const e = n.parentId;
164
+ t.has(e) || t.set(e, []), t.get(e).push(n);
165
+ }
166
+ return t;
167
+ });
168
+ function ut(t) {
169
+ var n;
170
+ return (((n = it.value.get(t)) == null ? void 0 : n.length) ?? 0) > 0;
171
+ }
172
+ function Mt(t) {
173
+ M.has(t) ? M.delete(t) : M.add(t);
174
+ }
175
+ const F = g(() => {
176
+ const t = [];
177
+ function n(e, a) {
178
+ for (const f of it.value.get(e) ?? [])
179
+ t.push({ task: f, depth: a }), ut(f.id) && !M.has(f.id) && n(f.id, a + 1);
180
+ }
181
+ return n(void 0, 0), t;
182
+ }), dt = g(() => {
183
+ const t = /* @__PURE__ */ new Map();
184
+ return F.value.forEach((n, e) => t.set(n.task.id, e)), t;
185
+ }), J = g(() => F.value.length * x.value), ct = g(() => {
186
+ const t = W((/* @__PURE__ */ new Date()).toISOString()), n = ot(Dt.value, t);
187
+ return n < 0 || n > Ct.value ? null : n * m.value;
188
+ });
189
+ function vt(t) {
190
+ return t.getDay() === 0 || t.getDay() === 6 || c.holidays.includes(N(t));
191
+ }
192
+ const Et = g(
193
+ () => I.value.map((t, n) => ({ day: t, index: n })).filter(({ day: t }) => vt(t)).map(({ index: t }) => ({ id: t, left: t * m.value }))
194
+ );
195
+ function gt(t, n) {
196
+ const e = t.currentTarget;
197
+ e.setPointerCapture(t.pointerId);
198
+ const a = t.clientX, f = W(n.start), y = W(n.end);
199
+ k.value = !0;
200
+ function p(b) {
201
+ const R = b.clientX - a, z = Math.round(R / m.value);
202
+ n.start = N(q(f, z)), n.end = N(q(y, z));
203
+ }
204
+ function D(b) {
205
+ e.releasePointerCapture(b.pointerId), e.removeEventListener("pointermove", p), e.removeEventListener("pointerup", D), k.value = !1, rt();
206
+ }
207
+ e.addEventListener("pointermove", p), e.addEventListener("pointerup", D);
208
+ }
209
+ function ft(t, n, e) {
210
+ const a = t.currentTarget;
211
+ a.setPointerCapture(t.pointerId);
212
+ const f = t.clientX, y = W(n.start), p = W(n.end);
213
+ k.value = !0;
214
+ function D(R) {
215
+ const z = R.clientX - f, K = Math.round(z / m.value);
216
+ if (e === "end") {
217
+ const B = q(p, K);
218
+ n.end = N(B.getTime() < y.getTime() ? y : B);
219
+ } else {
220
+ const B = q(y, K);
221
+ n.start = N(B.getTime() > p.getTime() ? p : B);
222
+ }
223
+ }
224
+ function b(R) {
225
+ a.releasePointerCapture(R.pointerId), a.removeEventListener("pointermove", D), a.removeEventListener("pointerup", b), k.value = !1, rt();
226
+ }
227
+ a.addEventListener("pointermove", D), a.addEventListener("pointerup", b);
228
+ }
229
+ function Lt(t) {
230
+ return `${Math.min(Math.max(t.progress ?? 0, 0), 100)}%`;
231
+ }
232
+ function Pt(t) {
233
+ const { left: n } = et(t);
234
+ return { left: `${n + m.value / 2}px`, ...ht(t) };
235
+ }
236
+ function ht(t) {
237
+ return t.color ? { backgroundColor: t.color } : {};
238
+ }
239
+ const S = E(null), nt = E({ top: 0, left: 0 });
240
+ function pt(t, n) {
241
+ S.value = n;
242
+ const e = t.currentTarget.getBoundingClientRect();
243
+ nt.value = { top: e.top - 8, left: e.left };
244
+ }
245
+ function mt() {
246
+ S.value = null;
247
+ }
248
+ function Wt(t) {
249
+ return `${t.getDate()}`;
250
+ }
251
+ function Y(t) {
252
+ const n = W(t), e = String(n.getMonth() + 1).padStart(2, "0"), a = String(n.getDate()).padStart(2, "0");
253
+ return `${e}/${a}/${n.getFullYear()}`;
254
+ }
255
+ function bt(t) {
256
+ return Y(t.start);
257
+ }
258
+ function Tt(t) {
259
+ return ot(W(t.start), W(t.end));
260
+ }
261
+ function yt(t) {
262
+ return Math.min(Math.max(t.progress ?? 0, 0), 100);
263
+ }
264
+ function _t(t) {
265
+ return `${yt(t)}%`;
266
+ }
267
+ function It(t) {
268
+ const n = t.toLocaleDateString(void 0, { month: "long" });
269
+ return n.charAt(0).toUpperCase() + n.slice(1);
270
+ }
271
+ const Rt = g(() => {
272
+ const t = [];
273
+ for (const n of I.value) {
274
+ const e = `${n.getFullYear()}-${n.getMonth()}`, a = t[t.length - 1];
275
+ a && a.key === e ? a.width += m.value : t.push({ key: e, label: It(n), width: m.value });
276
+ }
277
+ return t;
278
+ }), at = E(null), lt = E(null);
279
+ function Bt() {
280
+ at.value && lt.value && (at.value.scrollTop = lt.value.scrollTop);
281
+ }
282
+ const Xt = g(() => ({
283
+ width: `${tt.value}px`,
284
+ height: `${T}px`
285
+ }));
286
+ function Ht(t, n, e, a) {
287
+ if (n === a) return `M ${t},${n} H ${e}`;
288
+ const f = Math.max(m.value / 3, 8), y = Math.min(t + f, e);
289
+ return `M ${t},${n} H ${y} V ${a} H ${e}`;
290
+ }
291
+ const zt = g(() => {
292
+ const t = [];
293
+ for (const n of F.value) {
294
+ const e = n.task;
295
+ for (const a of e.dependsOn ?? []) {
296
+ const f = dt.value.get(a), y = dt.value.get(e.id);
297
+ if (f === void 0 || y === void 0) continue;
298
+ const p = l.value.find((Ot) => Ot.id === a);
299
+ if (!p) continue;
300
+ const D = et(p), b = et(e), R = D.left + D.width, z = f * x.value + x.value / 2, K = b.left - oe, B = y * x.value + x.value / 2;
301
+ t.push({ id: `${p.id}->${e.id}`, d: Ht(R, z, K, B) });
302
+ }
303
+ }
304
+ return t;
305
+ });
306
+ return kt(
307
+ () => c.leftWidth,
308
+ (t) => {
309
+ H.value || (L.value = t);
310
+ }
311
+ ), kt(
312
+ () => c.tasks,
313
+ (t) => {
314
+ k.value || (l.value = t.map((n) => ({ ...n })));
315
+ },
316
+ { deep: !0 }
317
+ ), (t, n) => (u(), d("div", {
318
+ class: "vga-root",
319
+ "data-vga-theme": i.theme,
320
+ style: s({ height: i.height })
321
+ }, [
322
+ o("div", {
323
+ class: "vga-left",
324
+ style: s({ width: `${G.value}px` })
325
+ }, [
326
+ o("div", {
327
+ class: "vga-left-header",
328
+ style: s({ height: `${T}px` })
329
+ }, [
330
+ o("div", {
331
+ class: "vga-left-col vga-col-name",
332
+ style: s({ width: `${j.value}px` })
333
+ }, v(_.value.taskName), 5),
334
+ o("div", {
335
+ class: "vga-left-col vga-col-date",
336
+ style: s({ width: `${i.startDateColumnWidth}px` })
337
+ }, v(_.value.startDate), 5),
338
+ o("div", {
339
+ class: "vga-left-col vga-col-progress",
340
+ style: s({ width: `${i.progressColumnWidth}px` })
341
+ }, v(_.value.progress), 5),
342
+ o("div", {
343
+ class: "vga-left-col vga-col-duration",
344
+ style: s({ width: `${i.durationColumnWidth}px` })
345
+ }, v(_.value.duration), 5)
346
+ ], 4),
347
+ o("div", {
348
+ ref_key: "leftBodyRef",
349
+ ref: at,
350
+ class: "vga-left-body"
351
+ }, [
352
+ (u(!0), d(O, null, A(F.value, (e) => (u(), d("div", {
353
+ key: e.task.id,
354
+ class: "vga-left-row",
355
+ style: s({ height: `${C(x)}px` })
356
+ }, [
357
+ o("div", {
358
+ class: "vga-left-col vga-col-name",
359
+ style: s({ width: `${j.value}px`, paddingLeft: `${8 + e.depth * 16}px` })
360
+ }, [
361
+ ut(e.task.id) ? (u(), d("span", {
362
+ key: 0,
363
+ class: "vga-toggle",
364
+ onClick: (a) => Mt(e.task.id)
365
+ }, v(M.has(e.task.id) ? "▶" : "▼"), 9, Vt)) : (u(), d("span", jt)),
366
+ o("span", qt, v(e.task.name), 1)
367
+ ], 4),
368
+ o("div", {
369
+ class: "vga-left-col vga-col-date",
370
+ style: s({ width: `${i.startDateColumnWidth}px` })
371
+ }, v(bt(e.task)), 5),
372
+ o("div", {
373
+ class: "vga-left-col vga-col-progress",
374
+ style: s({ width: `${i.progressColumnWidth}px` })
375
+ }, [
376
+ o("div", {
377
+ class: "vga-progress-fill",
378
+ style: s({ width: `${yt(e.task)}%` })
379
+ }, null, 4),
380
+ o("span", Jt, v(_t(e.task)), 1)
381
+ ], 4),
382
+ o("div", {
383
+ class: "vga-left-col vga-col-duration",
384
+ style: s({ width: `${i.durationColumnWidth}px` })
385
+ }, v(Tt(e.task)), 5)
386
+ ], 4))), 128))
387
+ ], 512)
388
+ ], 4),
389
+ P.value ? V("", !0) : (u(), d("div", {
390
+ key: 0,
391
+ class: "vga-resize-left",
392
+ style: s({ left: `${G.value}px` }),
393
+ onPointerdown: r
394
+ }, null, 36)),
395
+ o("button", {
396
+ type: "button",
397
+ class: "vga-collapse-toggle",
398
+ style: s({ left: `${G.value}px` }),
399
+ "aria-label": P.value ? "Expand task list" : "Collapse task list",
400
+ onClick: n[0] || (n[0] = (e) => P.value = !P.value)
401
+ }, v(P.value ? "›" : "‹"), 13, Kt),
402
+ o("div", {
403
+ ref_key: "rightRef",
404
+ ref: lt,
405
+ class: "vga-right",
406
+ onScroll: Bt
407
+ }, [
408
+ o("div", {
409
+ class: "vga-right-header",
410
+ style: s(Xt.value)
411
+ }, [
412
+ o("div", {
413
+ class: "vga-month-row",
414
+ style: s({ height: `${wt}px` })
415
+ }, [
416
+ (u(!0), d(O, null, A(Rt.value, (e) => (u(), d("div", {
417
+ key: e.key,
418
+ class: "vga-month-cell",
419
+ style: s({ width: `${e.width}px` })
420
+ }, v(e.label), 5))), 128))
421
+ ], 4),
422
+ o("div", {
423
+ class: "vga-day-row",
424
+ style: s({ height: `${xt}px` })
425
+ }, [
426
+ (u(!0), d(O, null, A(C(I), (e) => (u(), d("div", {
427
+ key: e.toISOString(),
428
+ class: Nt(["vga-day-cell", { "vga-day-cell--shaded": vt(e) }]),
429
+ style: s({ width: `${C(m)}px` })
430
+ }, v(Wt(e)), 7))), 128))
431
+ ], 4)
432
+ ], 4),
433
+ o("div", {
434
+ class: "vga-rows",
435
+ style: s({ width: `${C(tt)}px`, height: `${J.value}px` })
436
+ }, [
437
+ (u(!0), d(O, null, A(Et.value, (e) => (u(), d("div", {
438
+ key: `shade-${e.id}`,
439
+ class: "vga-weekend",
440
+ style: s({ left: `${e.left}px`, width: `${C(m)}px`, height: `${J.value}px` })
441
+ }, null, 4))), 128)),
442
+ ct.value !== null ? (u(), d("div", {
443
+ key: 0,
444
+ class: "vga-today-line",
445
+ style: s({ left: `${ct.value}px`, height: `${J.value}px` })
446
+ }, null, 4)) : V("", !0),
447
+ (u(), d("svg", {
448
+ class: "vga-edges",
449
+ width: C(tt),
450
+ height: J.value
451
+ }, [
452
+ n[1] || (n[1] = o("defs", null, [
453
+ o("marker", {
454
+ id: "vga-arrow",
455
+ viewBox: "0 0 10 10",
456
+ refX: "10",
457
+ refY: "5",
458
+ markerWidth: "7",
459
+ markerHeight: "7",
460
+ orient: "auto-start-reverse"
461
+ }, [
462
+ o("path", {
463
+ d: "M0,0 L10,5 L0,10 z",
464
+ fill: "var(--vga-edge)"
465
+ })
466
+ ])
467
+ ], -1)),
468
+ (u(!0), d(O, null, A(zt.value, (e) => (u(), d("path", {
469
+ key: e.id,
470
+ d: e.d,
471
+ class: "vga-edge-path",
472
+ "marker-end": "url(#vga-arrow)"
473
+ }, null, 8, Zt))), 128))
474
+ ], 8, Qt)),
475
+ (u(!0), d(O, null, A(F.value, (e) => (u(), d("div", {
476
+ key: e.task.id,
477
+ class: "vga-row",
478
+ style: s({ height: `${C(x)}px` })
479
+ }, [
480
+ i.showPlanned && C(st)(e.task) ? (u(), d("div", {
481
+ key: 0,
482
+ class: "vga-bar-planned",
483
+ style: s(C(st)(e.task))
484
+ }, null, 4)) : V("", !0),
485
+ e.task.milestone ? (u(), d("div", {
486
+ key: 1,
487
+ class: "vga-milestone",
488
+ style: s(Pt(e.task)),
489
+ onPointerdown: (a) => gt(a, e.task),
490
+ onMouseenter: (a) => pt(a, e.task),
491
+ onMouseleave: mt
492
+ }, null, 44, te)) : (u(), d("div", {
493
+ key: 2,
494
+ class: "vga-bar",
495
+ style: s([C(St)(e.task), ht(e.task)]),
496
+ onPointerdown: (a) => gt(a, e.task),
497
+ onMouseenter: (a) => pt(a, e.task),
498
+ onMouseleave: mt
499
+ }, [
500
+ o("div", {
501
+ class: "vga-bar-progress",
502
+ style: s({ width: Lt(e.task) })
503
+ }, null, 4),
504
+ o("span", ne, v(e.task.name), 1),
505
+ o("div", {
506
+ class: "vga-resize-handle vga-resize-handle--left",
507
+ onPointerdown: $t((a) => ft(a, e.task, "start"), ["stop"])
508
+ }, null, 40, ae),
509
+ o("div", {
510
+ class: "vga-resize-handle vga-resize-handle--right",
511
+ onPointerdown: $t((a) => ft(a, e.task, "end"), ["stop"])
512
+ }, null, 40, le)
513
+ ], 44, ee))
514
+ ], 4))), 128))
515
+ ], 4)
516
+ ], 544),
517
+ S.value ? (u(), d("div", {
518
+ key: 1,
519
+ class: "vga-tooltip",
520
+ style: s({ top: `${nt.value.top}px`, left: `${nt.value.left}px` })
521
+ }, [
522
+ o("strong", null, v(S.value.name), 1),
523
+ o("div", null, "Actual: " + v(Y(S.value.start)) + " → " + v(Y(S.value.end)), 1),
524
+ i.showPlanned && S.value.plannedStart && S.value.plannedEnd ? (u(), d("div", se, " Planned: " + v(Y(S.value.plannedStart)) + " → " + v(Y(S.value.plannedEnd)), 1)) : V("", !0)
525
+ ], 4)) : V("", !0)
526
+ ], 12, Yt));
527
+ }
528
+ }), ie = (i, h) => {
529
+ const T = i.__vccOpts || i;
530
+ for (const [w, c] of h)
531
+ T[w] = c;
532
+ return T;
533
+ }, de = /* @__PURE__ */ ie(re, [["__scopeId", "data-v-02881458"]]);
534
+ export {
535
+ de as GanttChart
536
+ };
@@ -0,0 +1 @@
1
+ (function(w,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],t):(w=typeof globalThis<"u"?globalThis:w||self,t(w.VueGanttAbsolute={},w.Vue))})(this,function(w,t){"use strict";function g(s){const d=new Date(s);return d.setHours(0,0,0,0),d}function V(s,d){return Math.round((d.getTime()-s.getTime())/864e5)}function mt(s,d,x){const h=t.computed(()=>{if(s.value.length===0)return g(new Date().toISOString());const r=s.value.flatMap(o=>[g(o.start).getTime(),...o.plannedStart?[g(o.plannedStart).getTime()]:[]]);return new Date(Math.min(...r))}),i=t.computed(()=>{if(s.value.length===0)return g(new Date().toISOString());const r=s.value.flatMap(o=>[g(o.end).getTime(),...o.plannedEnd?[g(o.plannedEnd).getTime()]:[]]);return new Date(Math.max(...r))}),L=t.computed(()=>Math.max(V(h.value,i.value)+1,1)),p=t.computed(()=>{const r=[];for(let o=0;o<L.value;o++)r.push(new Date(h.value.getTime()+o*864e5));return r}),y=t.computed(()=>L.value*d.value);function C(r){const o=g(r.start),u=g(r.end),E=V(h.value,o),M=V(o,u)+1;return{left:E*d.value,width:M*d.value}}function D(r){const{left:o,width:u}=C(r);return{left:`${o}px`,width:`${u}px`}}function _(r){if(!r.plannedStart||!r.plannedEnd)return null;const o=g(r.plannedStart),u=g(r.plannedEnd),E=V(h.value,o),M=V(o,u)+1;return{left:E*d.value,width:M*d.value}}function O(r){const o=_(r);return o?{left:`${o.left}px`,width:`${o.width}px`}:null}function I(r){return new Date(h.value.getTime()+r*864e5)}function v(r,o){return new Date(r.getTime()+o*864e5)}function W(r){return r.toISOString().slice(0,10)}return{rangeStart:h,rangeEnd:i,totalDays:L,days:p,totalWidth:y,barStyle:D,barRect:C,plannedBarStyle:O,plannedBarRect:_,diffDays:V,toDate:g,offsetToDate:I,addDays:v,formatISO:W,rowHeight:x}}const pt=["data-vga-theme"],ft=["onClick"],gt={key:1,class:"vga-toggle-spacer"},ut={class:"vga-name-text"},ht={class:"vga-progress-text"},yt=["aria-label"],kt=["width","height"],St=["d"],Et=["onPointerdown","onMouseenter"],Dt=["onPointerdown","onMouseenter"],vt={class:"vga-bar-label"},Bt=["onPointerdown"],$t=["onPointerdown"],wt={key:0,class:"vga-tooltip-planned"},q=24,J=32,xt=4,Ct=((s,d)=>{const x=s.__vccOpts||s;for(const[h,i]of d)x[h]=i;return x})(t.defineComponent({__name:"GanttChart",props:{tasks:{},dayWidth:{default:44},rowHeight:{default:40},leftWidth:{default:400},startDateColumnWidth:{default:110},progressColumnWidth:{default:90},durationColumnWidth:{default:90},height:{default:"auto"},holidays:{default:()=>[]},labels:{default:()=>({})},showPlanned:{type:Boolean,default:!0},theme:{default:"auto"}},emits:["update:tasks","update:leftWidth"],setup(s,{emit:d}){const x=q+J,h={taskName:"Task Name",startDate:"Start Date",progress:"Progress",duration:"Duration"},i=s,L=d,{dayWidth:p,rowHeight:y}=t.toRefs(i),C=t.computed(()=>({...h,...i.labels})),D=t.ref(i.leftWidth),_=t.ref(!1),O=t.computed(()=>i.startDateColumnWidth+i.progressColumnWidth+i.durationColumnWidth+60),I=t.computed(()=>Math.max(D.value-i.startDateColumnWidth-i.progressColumnWidth-i.durationColumnWidth,60)),v=t.ref(!1),W=t.computed(()=>v.value?0:D.value);function r(e){const a=e.currentTarget;a.setPointerCapture(e.pointerId);const n=e.clientX,l=D.value;_.value=!0;function c(m){const S=m.clientX-n;D.value=Math.max(l+S,O.value)}function f(m){a.releasePointerCapture(m.pointerId),a.removeEventListener("pointermove",c),a.removeEventListener("pointerup",f),_.value=!1,L("update:leftWidth",D.value)}a.addEventListener("pointermove",c),a.addEventListener("pointerup",f)}const o=t.ref(i.tasks.map(e=>({...e}))),u=t.ref(!1),E=t.reactive(new Set),{days:M,totalWidth:F,barStyle:Mt,barRect:Y,plannedBarStyle:K,diffDays:Q,toDate:B,addDays:X,formatISO:b,rangeStart:zt,totalDays:Nt}=mt(o,p,y);function Z(){L("update:tasks",o.value.map(e=>({...e})))}const tt=t.computed(()=>{const e=new Map;for(const a of o.value){const n=a.parentId;e.has(n)||e.set(n,[]),e.get(n).push(a)}return e});function et(e){var a;return(((a=tt.value.get(e))==null?void 0:a.length)??0)>0}function Vt(e){E.has(e)?E.delete(e):E.add(e)}const T=t.computed(()=>{const e=[];function a(n,l){for(const c of tt.value.get(n)??[])e.push({task:c,depth:l}),et(c.id)&&!E.has(c.id)&&a(c.id,l+1)}return a(void 0,0),e}),nt=t.computed(()=>{const e=new Map;return T.value.forEach((a,n)=>e.set(a.task.id,n)),e}),H=t.computed(()=>T.value.length*y.value),at=t.computed(()=>{const e=B(new Date().toISOString()),a=Q(zt.value,e);return a<0||a>Nt.value?null:a*p.value});function lt(e){return e.getDay()===0||e.getDay()===6||i.holidays.includes(b(e))}const Lt=t.computed(()=>M.value.map((e,a)=>({day:e,index:a})).filter(({day:e})=>lt(e)).map(({index:e})=>({id:e,left:e*p.value})));function ot(e,a){const n=e.currentTarget;n.setPointerCapture(e.pointerId);const l=e.clientX,c=B(a.start),f=B(a.end);u.value=!0;function m($){const z=$.clientX-l,P=Math.round(z/p.value);a.start=b(X(c,P)),a.end=b(X(f,P))}function S($){n.releasePointerCapture($.pointerId),n.removeEventListener("pointermove",m),n.removeEventListener("pointerup",S),u.value=!1,Z()}n.addEventListener("pointermove",m),n.addEventListener("pointerup",S)}function rt(e,a,n){const l=e.currentTarget;l.setPointerCapture(e.pointerId);const c=e.clientX,f=B(a.start),m=B(a.end);u.value=!0;function S(z){const P=z.clientX-c,A=Math.round(P/p.value);if(n==="end"){const N=X(m,A);a.end=b(N.getTime()<f.getTime()?f:N)}else{const N=X(f,A);a.start=b(N.getTime()>m.getTime()?m:N)}}function $(z){l.releasePointerCapture(z.pointerId),l.removeEventListener("pointermove",S),l.removeEventListener("pointerup",$),u.value=!1,Z()}l.addEventListener("pointermove",S),l.addEventListener("pointerup",$)}function _t(e){return`${Math.min(Math.max(e.progress??0,0),100)}%`}function Pt(e){const{left:a}=Y(e);return{left:`${a+p.value/2}px`,...st(e)}}function st(e){return e.color?{backgroundColor:e.color}:{}}const k=t.ref(null),G=t.ref({top:0,left:0});function it(e,a){k.value=a;const n=e.currentTarget.getBoundingClientRect();G.value={top:n.top-8,left:n.left}}function ct(){k.value=null}function Wt(e){return`${e.getDate()}`}function R(e){const a=B(e),n=String(a.getMonth()+1).padStart(2,"0"),l=String(a.getDate()).padStart(2,"0");return`${n}/${l}/${a.getFullYear()}`}function bt(e){return R(e.start)}function Tt(e){return Q(B(e.start),B(e.end))}function dt(e){return Math.min(Math.max(e.progress??0,0),100)}function Rt(e){return`${dt(e)}%`}function It(e){const a=e.toLocaleDateString(void 0,{month:"long"});return a.charAt(0).toUpperCase()+a.slice(1)}const Xt=t.computed(()=>{const e=[];for(const a of M.value){const n=`${a.getFullYear()}-${a.getMonth()}`,l=e[e.length-1];l&&l.key===n?l.width+=p.value:e.push({key:n,label:It(a),width:p.value})}return e}),U=t.ref(null),j=t.ref(null);function Ht(){U.value&&j.value&&(U.value.scrollTop=j.value.scrollTop)}const At=t.computed(()=>({width:`${F.value}px`,height:`${x}px`}));function Ot(e,a,n,l){if(a===l)return`M ${e},${a} H ${n}`;const c=Math.max(p.value/3,8),f=Math.min(e+c,n);return`M ${e},${a} H ${f} V ${l} H ${n}`}const Ft=t.computed(()=>{const e=[];for(const a of T.value){const n=a.task;for(const l of n.dependsOn??[]){const c=nt.value.get(l),f=nt.value.get(n.id);if(c===void 0||f===void 0)continue;const m=o.value.find(Yt=>Yt.id===l);if(!m)continue;const S=Y(m),$=Y(n),z=S.left+S.width,P=c*y.value+y.value/2,A=$.left-xt,N=f*y.value+y.value/2;e.push({id:`${m.id}->${n.id}`,d:Ot(z,P,A,N)})}}return e});return t.watch(()=>i.leftWidth,e=>{_.value||(D.value=e)}),t.watch(()=>i.tasks,e=>{u.value||(o.value=e.map(a=>({...a})))},{deep:!0}),(e,a)=>(t.openBlock(),t.createElementBlock("div",{class:"vga-root","data-vga-theme":s.theme,style:t.normalizeStyle({height:s.height})},[t.createElementVNode("div",{class:"vga-left",style:t.normalizeStyle({width:`${W.value}px`})},[t.createElementVNode("div",{class:"vga-left-header",style:t.normalizeStyle({height:`${x}px`})},[t.createElementVNode("div",{class:"vga-left-col vga-col-name",style:t.normalizeStyle({width:`${I.value}px`})},t.toDisplayString(C.value.taskName),5),t.createElementVNode("div",{class:"vga-left-col vga-col-date",style:t.normalizeStyle({width:`${s.startDateColumnWidth}px`})},t.toDisplayString(C.value.startDate),5),t.createElementVNode("div",{class:"vga-left-col vga-col-progress",style:t.normalizeStyle({width:`${s.progressColumnWidth}px`})},t.toDisplayString(C.value.progress),5),t.createElementVNode("div",{class:"vga-left-col vga-col-duration",style:t.normalizeStyle({width:`${s.durationColumnWidth}px`})},t.toDisplayString(C.value.duration),5)],4),t.createElementVNode("div",{ref_key:"leftBodyRef",ref:U,class:"vga-left-body"},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(T.value,n=>(t.openBlock(),t.createElementBlock("div",{key:n.task.id,class:"vga-left-row",style:t.normalizeStyle({height:`${t.unref(y)}px`})},[t.createElementVNode("div",{class:"vga-left-col vga-col-name",style:t.normalizeStyle({width:`${I.value}px`,paddingLeft:`${8+n.depth*16}px`})},[et(n.task.id)?(t.openBlock(),t.createElementBlock("span",{key:0,class:"vga-toggle",onClick:l=>Vt(n.task.id)},t.toDisplayString(E.has(n.task.id)?"▶":"▼"),9,ft)):(t.openBlock(),t.createElementBlock("span",gt)),t.createElementVNode("span",ut,t.toDisplayString(n.task.name),1)],4),t.createElementVNode("div",{class:"vga-left-col vga-col-date",style:t.normalizeStyle({width:`${s.startDateColumnWidth}px`})},t.toDisplayString(bt(n.task)),5),t.createElementVNode("div",{class:"vga-left-col vga-col-progress",style:t.normalizeStyle({width:`${s.progressColumnWidth}px`})},[t.createElementVNode("div",{class:"vga-progress-fill",style:t.normalizeStyle({width:`${dt(n.task)}%`})},null,4),t.createElementVNode("span",ht,t.toDisplayString(Rt(n.task)),1)],4),t.createElementVNode("div",{class:"vga-left-col vga-col-duration",style:t.normalizeStyle({width:`${s.durationColumnWidth}px`})},t.toDisplayString(Tt(n.task)),5)],4))),128))],512)],4),v.value?t.createCommentVNode("",!0):(t.openBlock(),t.createElementBlock("div",{key:0,class:"vga-resize-left",style:t.normalizeStyle({left:`${W.value}px`}),onPointerdown:r},null,36)),t.createElementVNode("button",{type:"button",class:"vga-collapse-toggle",style:t.normalizeStyle({left:`${W.value}px`}),"aria-label":v.value?"Expand task list":"Collapse task list",onClick:a[0]||(a[0]=n=>v.value=!v.value)},t.toDisplayString(v.value?"›":"‹"),13,yt),t.createElementVNode("div",{ref_key:"rightRef",ref:j,class:"vga-right",onScroll:Ht},[t.createElementVNode("div",{class:"vga-right-header",style:t.normalizeStyle(At.value)},[t.createElementVNode("div",{class:"vga-month-row",style:t.normalizeStyle({height:`${q}px`})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(Xt.value,n=>(t.openBlock(),t.createElementBlock("div",{key:n.key,class:"vga-month-cell",style:t.normalizeStyle({width:`${n.width}px`})},t.toDisplayString(n.label),5))),128))],4),t.createElementVNode("div",{class:"vga-day-row",style:t.normalizeStyle({height:`${J}px`})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(t.unref(M),n=>(t.openBlock(),t.createElementBlock("div",{key:n.toISOString(),class:t.normalizeClass(["vga-day-cell",{"vga-day-cell--shaded":lt(n)}]),style:t.normalizeStyle({width:`${t.unref(p)}px`})},t.toDisplayString(Wt(n)),7))),128))],4)],4),t.createElementVNode("div",{class:"vga-rows",style:t.normalizeStyle({width:`${t.unref(F)}px`,height:`${H.value}px`})},[(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(Lt.value,n=>(t.openBlock(),t.createElementBlock("div",{key:`shade-${n.id}`,class:"vga-weekend",style:t.normalizeStyle({left:`${n.left}px`,width:`${t.unref(p)}px`,height:`${H.value}px`})},null,4))),128)),at.value!==null?(t.openBlock(),t.createElementBlock("div",{key:0,class:"vga-today-line",style:t.normalizeStyle({left:`${at.value}px`,height:`${H.value}px`})},null,4)):t.createCommentVNode("",!0),(t.openBlock(),t.createElementBlock("svg",{class:"vga-edges",width:t.unref(F),height:H.value},[a[1]||(a[1]=t.createElementVNode("defs",null,[t.createElementVNode("marker",{id:"vga-arrow",viewBox:"0 0 10 10",refX:"10",refY:"5",markerWidth:"7",markerHeight:"7",orient:"auto-start-reverse"},[t.createElementVNode("path",{d:"M0,0 L10,5 L0,10 z",fill:"var(--vga-edge)"})])],-1)),(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(Ft.value,n=>(t.openBlock(),t.createElementBlock("path",{key:n.id,d:n.d,class:"vga-edge-path","marker-end":"url(#vga-arrow)"},null,8,St))),128))],8,kt)),(t.openBlock(!0),t.createElementBlock(t.Fragment,null,t.renderList(T.value,n=>(t.openBlock(),t.createElementBlock("div",{key:n.task.id,class:"vga-row",style:t.normalizeStyle({height:`${t.unref(y)}px`})},[s.showPlanned&&t.unref(K)(n.task)?(t.openBlock(),t.createElementBlock("div",{key:0,class:"vga-bar-planned",style:t.normalizeStyle(t.unref(K)(n.task))},null,4)):t.createCommentVNode("",!0),n.task.milestone?(t.openBlock(),t.createElementBlock("div",{key:1,class:"vga-milestone",style:t.normalizeStyle(Pt(n.task)),onPointerdown:l=>ot(l,n.task),onMouseenter:l=>it(l,n.task),onMouseleave:ct},null,44,Et)):(t.openBlock(),t.createElementBlock("div",{key:2,class:"vga-bar",style:t.normalizeStyle([t.unref(Mt)(n.task),st(n.task)]),onPointerdown:l=>ot(l,n.task),onMouseenter:l=>it(l,n.task),onMouseleave:ct},[t.createElementVNode("div",{class:"vga-bar-progress",style:t.normalizeStyle({width:_t(n.task)})},null,4),t.createElementVNode("span",vt,t.toDisplayString(n.task.name),1),t.createElementVNode("div",{class:"vga-resize-handle vga-resize-handle--left",onPointerdown:t.withModifiers(l=>rt(l,n.task,"start"),["stop"])},null,40,Bt),t.createElementVNode("div",{class:"vga-resize-handle vga-resize-handle--right",onPointerdown:t.withModifiers(l=>rt(l,n.task,"end"),["stop"])},null,40,$t)],44,Dt))],4))),128))],4)],544),k.value?(t.openBlock(),t.createElementBlock("div",{key:1,class:"vga-tooltip",style:t.normalizeStyle({top:`${G.value.top}px`,left:`${G.value.left}px`})},[t.createElementVNode("strong",null,t.toDisplayString(k.value.name),1),t.createElementVNode("div",null,"Actual: "+t.toDisplayString(R(k.value.start))+" → "+t.toDisplayString(R(k.value.end)),1),s.showPlanned&&k.value.plannedStart&&k.value.plannedEnd?(t.openBlock(),t.createElementBlock("div",wt," Planned: "+t.toDisplayString(R(k.value.plannedStart))+" → "+t.toDisplayString(R(k.value.plannedEnd)),1)):t.createCommentVNode("",!0)],4)):t.createCommentVNode("",!0)],12,pt))}}),[["__scopeId","data-v-02881458"]]);w.GanttChart=Ct,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})});
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "vue-gantt-absolute",
3
+ "version": "0.1.0",
4
+ "description": "Librería Gantt libre para Vue 3 (Composition API)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "vue",
9
+ "vue3",
10
+ "gantt",
11
+ "chart",
12
+ "timeline",
13
+ "composition-api"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/CristianCala/vue-gantt-absolute.git"
18
+ },
19
+ "homepage": "https://github.com/CristianCala/vue-gantt-absolute#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/CristianCala/vue-gantt-absolute/issues"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "main": "./dist/vue-gantt-absolute.umd.cjs",
27
+ "module": "./dist/vue-gantt-absolute.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/vue-gantt-absolute.js",
33
+ "require": "./dist/vue-gantt-absolute.umd.cjs"
34
+ },
35
+ "./style.css": "./dist/style.css"
36
+ },
37
+ "scripts": {
38
+ "dev": "vite",
39
+ "build": "vue-tsc -p tsconfig.build.json && vite build --config vite.lib.config.ts",
40
+ "preview": "vite preview",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "peerDependencies": {
44
+ "vue": "^3.3.0"
45
+ },
46
+ "devDependencies": {
47
+ "@vitejs/plugin-vue": "^5.0.0",
48
+ "typescript": "^5.4.0",
49
+ "vite": "^5.2.0",
50
+ "vue": "^3.4.0",
51
+ "vue-tsc": "^2.0.0"
52
+ }
53
+ }