@workiom/frappe-gantt 1.0.5

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/README.md ADDED
@@ -0,0 +1,241 @@
1
+ <div align="center" markdown="1">
2
+ <img src=".github/gantt-logo.jpg" width="80">
3
+ <h1>Frappe Gantt</h1>
4
+
5
+ **A modern, configurable, Gantt library for the web.**
6
+
7
+ </div>
8
+
9
+ ![Hero Image](.github/hero-image.png)
10
+
11
+ ## Frappe Gantt
12
+
13
+ Gantt charts are bar charts that visually illustrate a project's tasks, schedule, and dependencies. With Frappe Gantt, you can build beautiful, customizable, Gantt charts with ease.
14
+
15
+ You can use it anywhere from hobby projects to tracking the goals of your team at the worksplace.
16
+
17
+ [ERPNext](https://erpnext.com/) uses Frappe Gantt.
18
+
19
+ ### Motivation
20
+
21
+ We needed a Gantt View for ERPNext. Surprisingly, we couldn't find a visually appealing Gantt library that was open source - so we decided to build it. Initially, the design was heavily inspired by Google Gantt and DHTMLX.
22
+
23
+ ### Key Features
24
+
25
+ - **Customizable Views**: customize the timeline based on various time periods - day, hour, or year, you have it. You can also create your own views.
26
+ - **Ignore Periods**: exclude weekends and other holidays from your tasks' progress calculation.
27
+ - **Configure Anything**: spacing, edit access, labels, you can control it all. Change both the style and functionality to meet your needs.
28
+ - **Multi-lingual Support**: suitable for companies with an international base.
29
+
30
+ ## Usage
31
+
32
+ Install with:
33
+
34
+ ```bash
35
+ npm install frappe-gantt
36
+ ```
37
+
38
+ Include it in your HTML:
39
+
40
+ ```html
41
+ <script src="frappe-gantt.umd.js"></script>
42
+ <link rel="stylesheet" href="frappe-gantt.css" />
43
+ ```
44
+
45
+ Or from the CDN:
46
+
47
+ ```html
48
+ <script src="https://cdn.jsdelivr.net/npm/frappe-gantt/dist/frappe-gantt.umd.js"></script>
49
+ <link
50
+ rel="stylesheet"
51
+ href="https://cdn.jsdelivr.net/npm/frappe-gantt/dist/frappe-gantt.css"
52
+ />
53
+ ```
54
+
55
+ Start using Gantt:
56
+
57
+ ```js
58
+ let tasks = [
59
+ {
60
+ id: '1',
61
+ name: 'Redesign website',
62
+ start: '2016-12-28',
63
+ end: '2016-12-31',
64
+ progress: 20
65
+ },
66
+ ...
67
+ ]
68
+ let gantt = new Gantt("#gantt", tasks);
69
+
70
+ // Use .refresh to update the chart
71
+ gantt.tasks.append(...)
72
+ gantt.tasks.refresh()
73
+ ```
74
+
75
+ ### Configuration
76
+
77
+ Frappe Gantt offers a wide range of options to customize your chart.
78
+
79
+ | **Option** | **Description** | **Possible Values** | **Default** |
80
+ | ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
81
+ | `arrow_curve` | Curve radius of arrows connecting dependencies. | Any positive integer. | `5` |
82
+ | `auto_move_label` | Move task labels when user scrolls horizontally. | `true`, `false` | `false` |
83
+ | `bar_corner_radius` | Radius of the task bar corners (in pixels). | Any positive integer. | `3` |
84
+ | `bar_height` | Height of task bars (in pixels). | Any positive integer. | `30` |
85
+ | `container_height` | Height of the container. | `auto` - dynamic container height to fit all tasks - _or_ any positive integer (for pixels). | `auto` |
86
+ | `column_width` | Width of each column in the timeline. | Any positive integer. | 45 |
87
+ | `date_format` | Format for displaying dates. | Any valid JS date format string. | `YYYY-MM-DD` |
88
+ | `dependencies_type` | How dependent tasks behave when parent tasks move. | `fixed`, `finish-to-start`, `start-to-start`, `finish-to-finish`, `start-to-finish` | `fixed` |
89
+ | `upper_header_height` | Height of the upper header in the timeline (in pixels). | Any positive integer. | `45` |
90
+ | `lower_header_height` | Height of the lower header in the timeline (in pixels). | Any positive integer. | `30` |
91
+ | `snap_at` | Snap tasks at particular intervel while resizing or dragging. | Any _interval_ (see below) | `1d` |
92
+ | `infinite_padding` | Whether to extend timeline infinitely when user scrolls. | `true`, `false` | `true` |
93
+ | `holidays` | Highlighted holidays on the timeline. | Object mapping CSS colors to holiday types. Types can either be a) 'weekend', or b) array of _strings_ or _date objects_ or _objects_ in the format `{date: ..., label: ...}` | `{ 'var(--g-weekend-highlight-color)': 'weekend' }` |
94
+ | `is_weekend` | Determines whether a day is a weekend | Function | `(d) => d.getDay() === 0 \|\| d.getDay() === 6` |
95
+ | `ignore` | Ignored areas in the rendering | `weekend` _or_ Array of strings or date objects (`weekend` can be present to the array also). | `[]` |
96
+ | `language` | Language for localization. | ISO 639-1 codes like `en`, `fr`, `es`. | `en` |
97
+ | `lines` | Determines which grid lines to display. | `none` for no lines, `vertical` for only vertical lines, `horizontal` for only horizontal lines, `both` for complete grid. | `both` |
98
+ | `move_dependencies` | Whether moving a task automatically moves its dependencies. | `true`, `false` | `true` |
99
+ | `padding` | Padding around task bars (in pixels). | Any positive integer. | `18` |
100
+ | `popup_on` | Event to trigger the popup display. | `click` _or_ `hover` | `click` |
101
+ | `readonly_progress` | Disables editing task progress. | `true`, `false` | `false` |
102
+ | `readonly_dates` | Disables editing task dates. | `true`, `false` | `false` |
103
+ | `readonly` | Disables all editing features. | `true`, `false` | `false` |
104
+ | `scroll_to` | Determines the starting point when chart is rendered. | `today`, `start`, `end`, or a date string. | `today` |
105
+ | `show_expected_progress` | Shows expected progress for tasks. | `true`, `false` | `false` |
106
+ | `today_button` | Adds a button to navigate to today’s date. | `true`, `false` | `true` |
107
+ | `view_mode` | The initial view mode of the Gantt chart. | `Day`, `Week`, `Month`, `Year`. | `Day` |
108
+ | `view_mode_select` | Allows selecting the view mode from a dropdown. | `true`, `false` | `false` |
109
+
110
+ Apart from these ones, three options - `popup`, `view_modes` (plural, not singular), and `dependencies_type` - have additional details and are listed separately below.
111
+
112
+ #### Dependencies Type Configuration
113
+
114
+ The `dependencies_type` option controls how dependent tasks behave when their parent tasks are moved. This can be set globally for all tasks or overridden per task.
115
+
116
+ **Available Types:**
117
+
118
+ - **`fixed`** (default): Maintains backward compatibility. When `move_dependencies: true`, dependent tasks move together with parent during drag.
119
+
120
+ - **`finish-to-start`**: Most common in project management. The dependent task can only start after the parent finishes.
121
+ - Constraint: Dependent start date ≥ Parent end date
122
+ - Auto-update: If parent ends after dependent starts, dependent moves forward
123
+ - Example: Development can only start after Design finishes
124
+
125
+ - **`start-to-start`**: The dependent task can only start after the parent starts.
126
+ - Constraint: Dependent start date ≥ Parent start date
127
+ - Auto-update: If parent starts after dependent, dependent moves forward
128
+ - Example: Testing can start after Development starts (parallel work)
129
+
130
+ - **`finish-to-finish`**: The dependent task can only finish after the parent finishes.
131
+ - Constraint: Dependent end date ≥ Parent end date
132
+ - Auto-update: If parent ends after dependent, dependent extends
133
+ - Example: Documentation must finish after Development finishes
134
+
135
+ - **`start-to-finish`**: The dependent task can only finish after the parent starts (rare).
136
+ - Constraint: Dependent end date ≥ Parent start date
137
+ - Auto-update: If parent starts after dependent ends, dependent extends
138
+ - Example: Legacy system runs until new system starts
139
+
140
+ **Usage:**
141
+
142
+ ```js
143
+ // Global configuration
144
+ let gantt = new Gantt("#gantt", tasks, {
145
+ dependencies_type: 'finish-to-start'
146
+ });
147
+
148
+ // Per-task override
149
+ let tasks = [
150
+ {
151
+ id: 'design',
152
+ name: 'Design',
153
+ start: '2023-01-01',
154
+ end: '2023-01-05'
155
+ },
156
+ {
157
+ id: 'dev',
158
+ name: 'Development',
159
+ start: '2023-01-05',
160
+ end: '2023-01-15',
161
+ dependencies: 'design',
162
+ dependencies_type: 'finish-to-start' // Overrides global setting
163
+ }
164
+ ];
165
+ ```
166
+
167
+ **Behavior:**
168
+ - When a parent task is moved, dependents automatically adjust if constraints would be violated
169
+ - Users cannot drag dependent tasks to positions that violate constraints
170
+ - Updates cascade through dependency chains (A → B → C)
171
+ - All dependency types respect ignored dates/weekends
172
+
173
+ #### View Mode Configuration
174
+
175
+ The `view_modes` option determines all the available view modes for the chart. It should be an array of objects.
176
+
177
+ Each object can have the following properties:
178
+
179
+ - `name` (string) - the name of view mode.
180
+ - `padding` (interval) - the time above.
181
+ - `step` - the interval of each column
182
+ - `lower_text` (date format string _or_ function) - the format for text in lower header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string.
183
+ - `upper_text` (date format string _or_ function) - the format for text in upper header. Blank string for none. The function takes in `currentDate`, `previousDate`, and `lang`, and should return a string.
184
+ - `upper_text_frequency` (number) - how often the upper text has a value. Utilized in internal calculation to improve performance.
185
+ - `thick_line` (function) - takes in `currentDate`, returns Boolean determining whether the line for that date should be thicker than the others.
186
+
187
+ Three other options allow you to override general configuration for this view mode alone:
188
+
189
+ - `date_format`
190
+ - `column_width`
191
+ - `snap_at`
192
+ For details, see the above table.
193
+
194
+ #### Popup Configuration
195
+
196
+ `popup` is a function. If it returns
197
+
198
+ - `false`, there will be no popup.
199
+ - `undefined`, the popup will be rendered based on manipulation within the function
200
+ - a HTML string, the popup will be that string.
201
+
202
+ The function receives one object as an argument, containing:
203
+
204
+ - `task` - the task as an object
205
+ - `chart` - the entire Gantt chart
206
+ - `get_title`, `get_subtitle`, `get_details` (functions) - get the relevant section as a HTML node.
207
+ - `set_title`, `set_subtitle`, `set_details` (functions) - take in the HTML of the relevant section
208
+ - `add_action` (function) - accepts two parameters, `html` and `func` - respectively determining the HTML of the action and the callback when the action is pressed.
209
+
210
+ ### API
211
+
212
+ Frappe Gantt exposes a few helpful methods for you to interact with the chart:
213
+
214
+ | **Name** | **Description** | **Parameters** |
215
+ | ------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `.update_options` | Re-renders the chart after updating specific options. | `new_options` - object containing new options. |
217
+ | `.change_view_mode` | Updates the view mode. | `view_mode` - Name of view mode _or_ view mode object (see above) and `maintain_pos` - whether to go back to current scroll position after rerendering, defaults to `false`. |
218
+ | `.scroll_current` | Scrolls to the current date | No parameters. |
219
+ | `.update_task` | Re-renders a specific task bar alone | `task_id` - id of task and `new_details` - object containing the task properties to be updated. |
220
+
221
+ ## Development Setup
222
+
223
+ If you want to contribute enhancements or fixes:
224
+
225
+ 1. Clone this repo.
226
+ 2. `cd` into project directory.
227
+ 3. Run `pnpm i` to install dependencies.
228
+ 4. `pnpm run build` to build files - or `pnpm run build-dev` to build and watch for changes.
229
+ 5. Open `index.html` in your browser.
230
+ 6. Make your code changes and test them.
231
+
232
+ <br />
233
+ <br />
234
+ <div align="center" style="padding-top: 0.75rem;">
235
+ <a href="https://frappe.io" target="_blank">
236
+ <picture>
237
+ <source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
238
+ <img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
239
+ </picture>
240
+ </a>
241
+ </div>
@@ -0,0 +1 @@
1
+ :root{--g-arrow-color: #1f2937;--g-bar-color: #fff;--g-bar-border: #fff;--g-tick-color-thick: #ededed;--g-tick-color: #f3f3f3;--g-actions-background: #f3f3f3;--g-border-color: #ebeff2;--g-text-muted: #7c7c7c;--g-text-light: #fff;--g-text-dark: #171717;--g-progress-color: #dbdbdb;--g-handle-color: #37352f;--g-weekend-label-color: #dcdce4;--g-expected-progress: #c4c4e9;--g-header-background: #fff;--g-row-color: #fdfdfd;--g-row-border-color: #c7c7c7;--g-today-highlight: #37352f;--g-popup-actions: #ebeff2;--g-weekend-highlight-color: #f7f7f7}.gantt-container{line-height:14.5px;position:relative;overflow:auto;font-size:12px;height:var(--gv-grid-height);width:100%;border-radius:8px}.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:#fff;box-shadow:0 10px 24px -3px #0003;padding:10px;border-radius:5px;width:max-content;z-index:1000}.gantt-container .popup-wrapper .title{margin-bottom:2px;color:var(--g-text-dark);font-size:.85rem;font-weight:650;line-height:15px}.gantt-container .popup-wrapper .subtitle{color:var(--g-text-dark);font-size:.8rem;margin-bottom:5px}.gantt-container .popup-wrapper .details{color:var(--g-text-muted);font-size:.7rem}.gantt-container .popup-wrapper .actions{margin-top:10px;margin-left:3px}.gantt-container .popup-wrapper .action-btn{border:none;padding:5px 8px;background-color:var(--g-popup-actions);border-right:1px solid var(--g-text-light)}.gantt-container .popup-wrapper .action-btn:hover{background-color:brightness(97%)}.gantt-container .popup-wrapper .action-btn:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.gantt-container .popup-wrapper .action-btn:last-child{border-right:none;border-top-right-radius:4px;border-bottom-right-radius:4px}.gantt-container .grid-header{height:calc(var(--gv-lower-header-height) + var(--gv-upper-header-height) + 10px);background-color:var(--g-header-background);position:sticky;top:0;left:0;border-bottom:1px solid var(--g-row-border-color);z-index:1000}.gantt-container .lower-text,.gantt-container .upper-text{text-anchor:middle}.gantt-container .upper-header{height:var(--gv-upper-header-height)}.gantt-container .lower-header{height:var(--gv-lower-header-height)}.gantt-container .lower-text{font-size:12px;position:absolute;width:calc(var(--gv-column-width) * .8);height:calc(var(--gv-lower-header-height) * .8);margin:0 calc(var(--gv-column-width) * .1);align-content:center;text-align:center;color:var(--g-text-muted)}.gantt-container .upper-text{position:absolute;width:fit-content;font-weight:500;font-size:14px;color:var(--g-text-dark);height:calc(var(--gv-lower-header-height) * .66)}.gantt-container .current-upper{position:sticky;left:0!important;padding-left:17px;background:#fff}.gantt-container .side-header{position:sticky;top:0;right:0;float:right;z-index:1000;line-height:20px;font-weight:400;width:max-content;margin-left:auto;padding-right:10px;padding-top:10px;background:var(--g-header-background);display:flex}.gantt-container .side-header *{transition-property:background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;background-color:var(--g-actions-background);border-radius:.5rem;border:none;padding:5px 8px;color:var(--g-text-dark);font-size:14px;letter-spacing:.02em;font-weight:420;box-sizing:content-box;margin-right:5px}.gantt-container .side-header *:last-child{margin-right:0}.gantt-container .side-header *:hover{filter:brightness(97.5%)}.gantt-container .side-header select{width:60px;padding-top:2px;padding-bottom:2px}.gantt-container .side-header select:focus{outline:none}.gantt-container .date-range-highlight{background-color:var(--g-progress-color);border-radius:12px;height:calc(var(--gv-lower-header-height) - 6px);top:calc(var(--gv-upper-header-height) + 5px);position:absolute}.gantt-container .current-highlight{position:absolute;background:var(--g-today-highlight);width:1px;z-index:999}.gantt-container .current-ball-highlight{position:absolute;background:var(--g-today-highlight);z-index:1001;border-radius:50%}.gantt-container .current-date-highlight{background:var(--g-today-highlight);color:var(--g-text-light);border-radius:5px}.gantt-container .holiday-label{position:absolute;top:0;left:0;opacity:0;z-index:1000;background:--g-weekend-label-color;border-radius:5px;padding:2px 5px}.gantt-container .holiday-label.show{opacity:100}.gantt-container .extras{position:sticky;left:0}.gantt-container .extras .adjust{position:absolute;left:8px;top:calc(var(--gv-grid-height) - 60px);background-color:#000000b3;color:#fff;border:none;padding:8px;border-radius:3px}.gantt-container .hide{display:none}.gantt{user-select:none;-webkit-user-select:none;position:absolute}.gantt .grid-background{fill:none}.gantt .grid-row{fill:var(--g-row-color)}.gantt .row-line{stroke:var(--g-border-color)}.gantt .tick{stroke:var(--g-tick-color);stroke-width:.4}.gantt .tick.thick{stroke:var(--g-tick-color-thick);stroke-width:.7}.gantt .arrow{fill:none;stroke:var(--g-arrow-color);stroke-width:1.5}.gantt .bar-wrapper .bar{fill:var(--g-bar-color);stroke:var(--g-bar-border);stroke-width:0;transition:stroke-width .3s ease}.gantt .bar-progress{fill:var(--g-progress-color);border-radius:4px}.gantt .bar-expected-progress{fill:var(--g-expected-progress)}.gantt .bar-invalid{fill:transparent;stroke:var(--g-bar-border);stroke-width:1;stroke-dasharray:5}:is(.gantt .bar-invalid)~.bar-label{fill:var(--g-text-light)}.gantt .bar-label{fill:var(--g-text-dark);dominant-baseline:central;font-family:Helvetica;font-size:13px;font-weight:400}.gantt .bar-label.big{fill:var(--g-text-dark);text-anchor:start}.gantt .handle{fill:var(--g-handle-color);opacity:0;transition:opacity .3s ease}.gantt .handle.active,.gantt .handle.visible{cursor:ew-resize;opacity:1}.gantt .handle.progress{fill:var(--g-text-muted)}.gantt .bar-wrapper{cursor:pointer}.gantt .bar-wrapper .bar{outline:1px solid var(--g-row-border-color);border-radius:3px}.gantt .bar-wrapper:hover .bar{transition:transform .3s ease}.gantt .bar-wrapper:hover .date-range-highlight{display:block}