gantt-renderer 0.5.0 → 0.7.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/dist/index.mjs CHANGED
@@ -39,17 +39,20 @@ const taskBase = {
39
39
  const TaskLeafSchema = z.object({
40
40
  ...taskBase,
41
41
  kind: z.literal("task"),
42
- /** Duration in hours. Must be positive; use `kind: 'milestone'` for zero-duration points. */
43
- durationHours: z.number().int().positive(),
42
+ /** ISO date: YYYY-MM-DD */
43
+ endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
44
44
  /** 0–100 completion percentage (integer). */
45
45
  percentComplete: z.number().int().min(0).max(100).default(0)
46
+ }).refine((t) => t.endDate >= t.startDate, {
47
+ message: "endDate must be on or after startDate",
48
+ path: ["endDate"]
46
49
  });
47
50
  /** @internal */
48
51
  const TaskProjectSchema = z.object({
49
52
  ...taskBase,
50
53
  kind: z.literal("project"),
51
- /** Duration in hours. Must be positive; use `kind: 'milestone'` for zero-duration points. */
52
- durationHours: z.number().int().positive(),
54
+ /** ISO date: YYYY-MM-DD */
55
+ endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
53
56
  /** 0–100 completion percentage (integer). */
54
57
  percentComplete: z.number().int().min(0).max(100).default(0),
55
58
  /**
@@ -59,6 +62,9 @@ const TaskProjectSchema = z.object({
59
62
  * @default true
60
63
  */
61
64
  open: z.boolean().default(true)
65
+ }).refine((t) => t.endDate >= t.startDate, {
66
+ message: "endDate must be on or after startDate",
67
+ path: ["endDate"]
62
68
  });
63
69
  /** @internal */
64
70
  const TaskMilestoneSchema = z.object({
@@ -151,8 +157,12 @@ const GanttInputSchema = z.object({
151
157
  /**
152
158
  * Parses raw external data.
153
159
  *
160
+ * Compile-time generics enforce the `data` shape on tasks and links.
161
+ * Runtime validation is done by the zod schemas; the `data` field is
162
+ * validated as a generic object at runtime regardless of the type parameter.
163
+ *
154
164
  * @param raw - The unvalidated input from the consumer.
155
- * @returns The parsed and validated {@link GanttInput}.
165
+ * @returns The parsed and validated input with `data` typed per the type parameters.
156
166
  * @throws {import('zod').ZodError} On schema validation failure.
157
167
  */
158
168
  function parseGanttInput(raw) {
@@ -466,7 +476,8 @@ const EN_US_LABELS = {
466
476
  ariaMilestone: "Milestone {0}",
467
477
  addSubtaskTitle: "Add subtask",
468
478
  columnTaskName: "Task name",
469
- columnStartDate: "Start time",
479
+ columnStartDate: "Start",
480
+ columnEndDate: "End",
470
481
  columnDuration: "Duration",
471
482
  columnQuarter: "Q"
472
483
  };
@@ -477,6 +488,134 @@ const CHART_LOCALE_EN_US = {
477
488
  weekNumbering: "iso",
478
489
  weekendDays: [0, 6]
479
490
  };
491
+ const CHART_LOCALE_EN_GB = {
492
+ code: "en-GB",
493
+ labels: {
494
+ ariaTask: "Task {0}",
495
+ ariaMilestone: "Milestone {0}",
496
+ addSubtaskTitle: "Add subtask",
497
+ columnTaskName: "Task name",
498
+ columnStartDate: "Start",
499
+ columnEndDate: "End",
500
+ columnDuration: "Duration",
501
+ columnQuarter: "Q"
502
+ },
503
+ weekStartsOn: 1,
504
+ weekNumbering: "iso",
505
+ weekendDays: [0, 6]
506
+ };
507
+ const CHART_LOCALE_DE_DE = {
508
+ code: "de-DE",
509
+ labels: {
510
+ ariaTask: "Aufgabe {0}",
511
+ ariaMilestone: "Meilenstein {0}",
512
+ addSubtaskTitle: "Teilaufgabe hinzufügen",
513
+ columnTaskName: "Aufgabenname",
514
+ columnStartDate: "Start",
515
+ columnEndDate: "Ende",
516
+ columnDuration: "Dauer",
517
+ columnQuarter: "Q"
518
+ },
519
+ weekStartsOn: 1,
520
+ weekNumbering: "iso",
521
+ weekendDays: [0, 6]
522
+ };
523
+ const CHART_LOCALE_FR_FR = {
524
+ code: "fr-FR",
525
+ labels: {
526
+ ariaTask: "Tâche {0}",
527
+ ariaMilestone: "Jalon {0}",
528
+ addSubtaskTitle: "Ajouter une sous-tâche",
529
+ columnTaskName: "Nom de la tâche",
530
+ columnStartDate: "Début",
531
+ columnEndDate: "Fin",
532
+ columnDuration: "Durée",
533
+ columnQuarter: "T"
534
+ },
535
+ weekStartsOn: 1,
536
+ weekNumbering: "iso",
537
+ weekendDays: [0, 6]
538
+ };
539
+ const CHART_LOCALE_ES_ES = {
540
+ code: "es-ES",
541
+ labels: {
542
+ ariaTask: "Tarea {0}",
543
+ ariaMilestone: "Hito {0}",
544
+ addSubtaskTitle: "Añadir subtarea",
545
+ columnTaskName: "Nombre de tarea",
546
+ columnStartDate: "Inicio",
547
+ columnEndDate: "Fin",
548
+ columnDuration: "Duración",
549
+ columnQuarter: "T"
550
+ },
551
+ weekStartsOn: 1,
552
+ weekNumbering: "iso",
553
+ weekendDays: [0, 6]
554
+ };
555
+ const CHART_LOCALE_IT_IT = {
556
+ code: "it-IT",
557
+ labels: {
558
+ ariaTask: "Attività {0}",
559
+ ariaMilestone: "Pietra miliare {0}",
560
+ addSubtaskTitle: "Aggiungi sottoattività",
561
+ columnTaskName: "Nome attività",
562
+ columnStartDate: "Inizio",
563
+ columnEndDate: "Fine",
564
+ columnDuration: "Durata",
565
+ columnQuarter: "T"
566
+ },
567
+ weekStartsOn: 1,
568
+ weekNumbering: "iso",
569
+ weekendDays: [0, 6]
570
+ };
571
+ const CHART_LOCALE_PT_PT = {
572
+ code: "pt-PT",
573
+ labels: {
574
+ ariaTask: "Tarefa {0}",
575
+ ariaMilestone: "Marco {0}",
576
+ addSubtaskTitle: "Adicionar subtarefa",
577
+ columnTaskName: "Nome da tarefa",
578
+ columnStartDate: "Início",
579
+ columnEndDate: "Fim",
580
+ columnDuration: "Duração",
581
+ columnQuarter: "T"
582
+ },
583
+ weekStartsOn: 1,
584
+ weekNumbering: "iso",
585
+ weekendDays: [0, 6]
586
+ };
587
+ const CHART_LOCALE_ZH_CN = {
588
+ code: "zh-CN",
589
+ labels: {
590
+ ariaTask: "任务 {0}",
591
+ ariaMilestone: "里程碑 {0}",
592
+ addSubtaskTitle: "添加子任务",
593
+ columnTaskName: "任务名称",
594
+ columnStartDate: "开始",
595
+ columnEndDate: "结束",
596
+ columnDuration: "工期",
597
+ columnQuarter: "季"
598
+ },
599
+ weekStartsOn: 1,
600
+ weekNumbering: "us",
601
+ weekendDays: [0, 6]
602
+ };
603
+ const CHART_LOCALE_JA_JP = {
604
+ code: "ja-JP",
605
+ labels: {
606
+ ariaTask: "タスク {0}",
607
+ ariaMilestone: "マイルストーン {0}",
608
+ addSubtaskTitle: "サブタスクを追加",
609
+ columnTaskName: "タスク名",
610
+ columnStartDate: "開始",
611
+ columnEndDate: "終了",
612
+ columnDuration: "期間",
613
+ columnQuarter: "Q"
614
+ },
615
+ weekStartsOn: 0,
616
+ weekNumbering: "us",
617
+ weekendDays: [0, 6]
618
+ };
480
619
  function tryGetWeekInfo(code) {
481
620
  try {
482
621
  if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
@@ -759,6 +898,16 @@ function formatUpperLabel(date, scale, locale) {
759
898
  }
760
899
  }
761
900
  /**
901
+ * Returns the number of days in an inclusive range from `start` to `end`.
902
+ *
903
+ * @param start - The start date.
904
+ * @param end - The end date.
905
+ * @returns The number of days, inclusive.
906
+ */
907
+ function getRangeDays(start, end) {
908
+ return Math.round(diffDays(start, end)) + 1;
909
+ }
910
+ /**
762
911
  * Formats a `YYYY-MM-DD` string for display in the grid.
763
912
  *
764
913
  * @param dateStr - An ISO-8601 date string in `YYYY-MM-DD` format.
@@ -871,7 +1020,7 @@ function createPixelMapper(scale, viewportStart) {
871
1020
  const originMs = viewportStart.getTime();
872
1021
  const pxPerMs = columnWidth / msPerColumn;
873
1022
  const msPerPx = msPerColumn / columnWidth;
874
- const msPerHour = 36e5;
1023
+ const msPerDay = 864e5;
875
1024
  return {
876
1025
  originMs,
877
1026
  columnWidth,
@@ -881,11 +1030,11 @@ function createPixelMapper(scale, viewportStart) {
881
1030
  toDate(x) {
882
1031
  return new Date(originMs + x * msPerPx);
883
1032
  },
884
- durationToWidth(hours) {
885
- return hours * msPerHour * pxPerMs;
1033
+ durationDaysToWidth(days) {
1034
+ return days * msPerDay * pxPerMs;
886
1035
  },
887
- widthToDuration(px) {
888
- return px * msPerPx / msPerHour;
1036
+ widthToDurationDays(px) {
1037
+ return px * msPerPx / msPerDay;
889
1038
  }
890
1039
  };
891
1040
  }
@@ -934,7 +1083,8 @@ function computeLayout(rows, mapper) {
934
1083
  });
935
1084
  continue;
936
1085
  }
937
- const width = Math.max(mapper.durationToWidth(task.durationHours), 4);
1086
+ const days = getRangeDays(start, parseDate(task.endDate));
1087
+ const width = Math.max(mapper.durationDaysToWidth(days), 4);
938
1088
  const progressWidth = width * Math.min(1, Math.max(0, (task.percentComplete ?? 0) / 100));
939
1089
  result.set(task.id, {
940
1090
  taskId: task.id,
@@ -978,7 +1128,7 @@ function deriveViewport(tasks, paddingHours = 48) {
978
1128
  const start = parseDate(task.startDate);
979
1129
  if (start.getTime() < minMs) minMs = start.getTime();
980
1130
  if (task.kind !== "milestone") {
981
- const end = addHours(start, task.durationHours);
1131
+ const end = parseDate(task.endDate);
982
1132
  if (end.getTime() > maxMs) maxMs = end.getTime();
983
1133
  } else if (start.getTime() > maxMs) maxMs = start.getTime();
984
1134
  }
@@ -1490,17 +1640,10 @@ const DEFAULT_GRID_COLUMNS = [
1490
1640
  },
1491
1641
  {
1492
1642
  id: "startDate",
1493
- header: "Start time",
1643
+ header: "Start",
1494
1644
  width: "90px",
1495
1645
  field: "startDate",
1496
- format: (value, _task, _row, locale) => formatDisplayDate(String(value), locale)
1497
- },
1498
- {
1499
- id: "durationHours",
1500
- header: "Duration",
1501
- width: "68px",
1502
- field: "durationHours",
1503
- format: (value) => value > 0 ? String(value) : "—"
1646
+ format: (value, _task, _row, locale) => formatDisplayDate(value, locale)
1504
1647
  },
1505
1648
  {
1506
1649
  id: "actions",
@@ -1529,13 +1672,6 @@ function gridColumnDefaults(locale) {
1529
1672
  field: "startDate",
1530
1673
  format: (value, _task, _row, loc) => formatDisplayDate(String(value), loc)
1531
1674
  },
1532
- {
1533
- id: "durationHours",
1534
- header: locale.labels?.columnDuration ?? EN_US_LABELS.columnDuration,
1535
- width: "68px",
1536
- field: "durationHours",
1537
- format: (value) => value > 0 ? String(value) : "—"
1538
- },
1539
1675
  {
1540
1676
  id: "actions",
1541
1677
  header: "",
@@ -1601,13 +1737,13 @@ function toTask$1(node) {
1601
1737
  case "task": return {
1602
1738
  ...base,
1603
1739
  kind: "task",
1604
- durationHours: node.durationHours,
1740
+ endDate: node.endDate,
1605
1741
  percentComplete: node.percentComplete
1606
1742
  };
1607
1743
  case "project": return {
1608
1744
  ...base,
1609
1745
  kind: "project",
1610
- durationHours: node.durationHours,
1746
+ endDate: node.endDate,
1611
1747
  percentComplete: node.percentComplete,
1612
1748
  open: node.open
1613
1749
  };
@@ -1619,7 +1755,7 @@ function toTask$1(node) {
1619
1755
  }
1620
1756
  function getTaskField(task, field) {
1621
1757
  switch (field) {
1622
- case "durationHours": return task.kind !== "milestone" ? task.durationHours : void 0;
1758
+ case "endDate": return task.kind !== "milestone" ? task.endDate : void 0;
1623
1759
  case "percentComplete": return task.kind !== "milestone" ? task.percentComplete : void 0;
1624
1760
  case "open": return task.kind === "project" ? task.open : void 0;
1625
1761
  default: return task[field];
@@ -1716,14 +1852,17 @@ function buildAddButton(row, cbs, locale) {
1716
1852
  });
1717
1853
  return btn;
1718
1854
  }
1719
- function buildCell(column, row, expandedIds, cbs, locale) {
1855
+ function buildActionsPlaceholder() {
1856
+ return el("div");
1857
+ }
1858
+ function buildCell(column, row, expandedIds, cbs, locale, showAddTaskButton) {
1720
1859
  switch (column.id) {
1721
1860
  case "name": return buildTreeNameCell(row, expandedIds, cbs);
1722
- case "actions": return buildAddButton(row, cbs, locale);
1861
+ case "actions": return showAddTaskButton ? buildAddButton(row, cbs, locale) : buildActionsPlaceholder();
1723
1862
  default: return buildDataCell(row, column, locale);
1724
1863
  }
1725
1864
  }
1726
- function buildRow(row, selectedId, expandedIds, cbs, columns, locale) {
1865
+ function buildRow(row, selectedId, expandedIds, cbs, columns, locale, showAddTaskButton) {
1727
1866
  const selected = row.id === selectedId;
1728
1867
  const wrapper = el("div");
1729
1868
  wrapper.className = "gantt-row";
@@ -1755,7 +1894,7 @@ function buildRow(row, selectedId, expandedIds, cbs, columns, locale) {
1755
1894
  cbs.onTaskClick(row.id);
1756
1895
  }
1757
1896
  });
1758
- for (const column of visibleColumns(columns)) wrapper.append(buildCell(column, row, expandedIds, cbs, locale));
1897
+ for (const column of visibleColumns(columns)) wrapper.append(buildCell(column, row, expandedIds, cbs, locale, showAddTaskButton));
1759
1898
  return wrapper;
1760
1899
  }
1761
1900
  /**
@@ -1765,8 +1904,9 @@ function buildRow(row, selectedId, expandedIds, cbs, columns, locale) {
1765
1904
  * @param state - The current chart state.
1766
1905
  * @param cbs - The left pane callbacks.
1767
1906
  * @param columns - The grid column schema.
1907
+ * @param showAddTaskButton - Whether to render the add-subtask button in the actions column.
1768
1908
  */
1769
- function renderLeftPane(container, state, cbs, columns) {
1909
+ function renderLeftPane(container, state, cbs, columns, showAddTaskButton = true) {
1770
1910
  const { allRows, selectedId, expandedIds, startIndex, endIndex, paddingTop, paddingBottom, locale } = state;
1771
1911
  const frag = document.createDocumentFragment();
1772
1912
  if (paddingTop > 0) {
@@ -1774,7 +1914,7 @@ function renderLeftPane(container, state, cbs, columns) {
1774
1914
  spacer.style.height = `${paddingTop}px`;
1775
1915
  frag.append(spacer);
1776
1916
  }
1777
- for (const row of allRows.slice(startIndex, endIndex + 1)) frag.append(buildRow(row, selectedId, expandedIds, cbs, columns, locale));
1917
+ for (const row of allRows.slice(startIndex, endIndex + 1)) frag.append(buildRow(row, selectedId, expandedIds, cbs, columns, locale, showAddTaskButton));
1778
1918
  if (paddingBottom > 0) {
1779
1919
  const spacer = el("div");
1780
1920
  spacer.style.height = `${paddingBottom}px`;
@@ -2067,13 +2207,13 @@ function toTask(node) {
2067
2207
  case "task": return {
2068
2208
  ...base,
2069
2209
  kind: "task",
2070
- durationHours: node.durationHours,
2210
+ endDate: node.endDate,
2071
2211
  percentComplete: node.percentComplete
2072
2212
  };
2073
2213
  case "project": return {
2074
2214
  ...base,
2075
2215
  kind: "project",
2076
- durationHours: node.durationHours,
2216
+ endDate: node.endDate,
2077
2217
  percentComplete: node.percentComplete,
2078
2218
  open: node.open
2079
2219
  };
@@ -2107,22 +2247,22 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
2107
2247
  const startX = e.clientX;
2108
2248
  const originDate = parseDate(task.startDate);
2109
2249
  const mapper = getMapper();
2110
- let lastHours = 0;
2250
+ let lastDays = 0;
2111
2251
  function onMove(me) {
2112
2252
  const dx = me.clientX - startX;
2113
- lastHours = Math.round(mapper.widthToDuration(dx));
2253
+ lastDays = Math.round(mapper.widthToDurationDays(dx));
2114
2254
  cbs.onTaskMove?.({
2115
2255
  id: task.id,
2116
- startDate: addHours(originDate, lastHours)
2256
+ startDate: addDays(originDate, lastDays)
2117
2257
  });
2118
2258
  }
2119
2259
  function onUp() {
2120
2260
  window.removeEventListener("pointermove", onMove);
2121
2261
  window.removeEventListener("pointerup", onUp);
2122
2262
  barEl.style.cursor = "grab";
2123
- if (lastHours !== 0) cbs._onTaskMoveFinal?.({
2263
+ if (lastDays !== 0) cbs._onTaskMoveFinal?.({
2124
2264
  id: task.id,
2125
- startDate: addHours(originDate, lastHours)
2265
+ startDate: addDays(originDate, lastDays)
2126
2266
  });
2127
2267
  }
2128
2268
  barEl.style.cursor = "grabbing";
@@ -2137,16 +2277,16 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
2137
2277
  resizeHandleEl.setPointerCapture(e.pointerId);
2138
2278
  } catch {}
2139
2279
  const startX = e.clientX;
2140
- const origDur = task.kind !== "milestone" ? task.durationHours : 0;
2280
+ if (task.kind === "milestone") return;
2281
+ const origEnd = parseDate(task.endDate);
2141
2282
  const mapper = getMapper();
2142
- let lastDuration = origDur;
2283
+ let lastEnd = origEnd;
2143
2284
  function onMove(me) {
2144
2285
  const dx = me.clientX - startX;
2145
- const hoursDelta = Math.round(mapper.widthToDuration(dx));
2146
- lastDuration = Math.max(1, origDur + hoursDelta);
2286
+ lastEnd = addDays(origEnd, Math.round(mapper.widthToDurationDays(dx)));
2147
2287
  cbs.onTaskResize?.({
2148
2288
  id: task.id,
2149
- durationHours: lastDuration
2289
+ endDate: lastEnd.toISOString().slice(0, 10)
2150
2290
  });
2151
2291
  }
2152
2292
  function onUp() {
@@ -2154,7 +2294,7 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
2154
2294
  window.removeEventListener("pointerup", onUp);
2155
2295
  cbs._onTaskResizeFinal?.({
2156
2296
  id: task.id,
2157
- durationHours: lastDuration
2297
+ endDate: lastEnd.toISOString().slice(0, 10)
2158
2298
  });
2159
2299
  }
2160
2300
  window.addEventListener("pointermove", onMove);
@@ -2920,9 +3060,12 @@ const OVERSCAN = 4;
2920
3060
  * Validates input, builds a DOM tree, and renders a full interactive chart
2921
3061
  * inside the given container element.
2922
3062
  *
3063
+ * @param TTaskData - The type of the optional `data` property on tasks. Defaults to `never`.
3064
+ * @param TLinkData - The type of the optional `data` property on links. Defaults to `never`.
3065
+ *
2923
3066
  * @example
2924
3067
  * ```ts
2925
- * const chart = new GanttChart(document.getElementById('chart')!, input, {
3068
+ * const chart = new GanttChart(document.getElementById('chart')!, {
2926
3069
  * locale: 'de-DE',
2927
3070
  * theme: 'dark',
2928
3071
  * });
@@ -2948,6 +3091,7 @@ var GanttChart = class {
2948
3091
  #timelineMinWidth;
2949
3092
  #columns;
2950
3093
  #leftPaneDefaultWidth;
3094
+ #showAddTaskButton;
2951
3095
  #weekendDays;
2952
3096
  #specialDaysByDate;
2953
3097
  #expandedIds;
@@ -2979,6 +3123,7 @@ var GanttChart = class {
2979
3123
  this.#locale = resolveChartLocale(opts.locale);
2980
3124
  this.#columns = opts.gridColumns ?? gridColumnDefaults(this.#locale);
2981
3125
  this.#leftPaneDefaultWidth = opts.leftPaneWidth ?? gridNaturalWidth(this.#columns);
3126
+ this.#showAddTaskButton = opts.showAddTaskButton ?? true;
2982
3127
  this.#height = opts.height ?? 500;
2983
3128
  this.#timelineMinWidth = opts.timelineMinWidth ?? 220;
2984
3129
  this.#weekendDays = normalizeWeekendDays(opts.weekendDays ?? this.#locale.weekendDays);
@@ -3030,8 +3175,7 @@ var GanttChart = class {
3030
3175
  _onTaskMoveFinal: async (payload) => {
3031
3176
  const task = this.#findTask(payload.id);
3032
3177
  if (task !== void 0) {
3033
- const durationHours = task.kind !== "milestone" ? task.durationHours : 0;
3034
- const newEndDate = addHours(payload.startDate, durationHours);
3178
+ const newEndDate = task.kind !== "milestone" ? parseDate(task.endDate) : payload.startDate;
3035
3179
  const result = this.#callbacks.onTaskMove?.({
3036
3180
  task,
3037
3181
  newStartDate: payload.startDate,
@@ -3057,17 +3201,18 @@ var GanttChart = class {
3057
3201
  const task = this.#input?.tasks.find((t) => t.id === payload.id);
3058
3202
  if (task !== void 0) this.#dragOriginals.set(payload.id, task);
3059
3203
  }
3060
- this.#patchTask(payload.id, { durationHours: payload.durationHours });
3204
+ this.#patchTask(payload.id, { endDate: payload.endDate });
3061
3205
  this.#scheduleRender();
3062
3206
  },
3063
3207
  _onTaskResizeFinal: async (payload) => {
3064
3208
  const task = this.#findTask(payload.id);
3065
- if (task !== void 0) {
3209
+ if (task !== void 0 && task.kind !== "milestone") {
3066
3210
  const newStartDate = parseDate(task.startDate);
3067
- const newEndDate = addHours(newStartDate, task.kind !== "milestone" ? task.durationHours : 0);
3211
+ const newEndDate = parseDate(task.endDate);
3212
+ const newDurationHours = diffHours(newEndDate, newStartDate);
3068
3213
  const result = this.#callbacks.onTaskResize?.({
3069
3214
  task,
3070
- newDurationHours: payload.durationHours,
3215
+ newDurationHours,
3071
3216
  newStartDate,
3072
3217
  newEndDate,
3073
3218
  instance: this
@@ -3075,11 +3220,11 @@ var GanttChart = class {
3075
3220
  if (result instanceof Promise) {
3076
3221
  if (!await result) {
3077
3222
  const original = this.#dragOriginals.get(payload.id);
3078
- if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { durationHours: original.durationHours });
3223
+ if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { endDate: original.endDate });
3079
3224
  }
3080
3225
  } else if (!result) {
3081
3226
  const original = this.#dragOriginals.get(payload.id);
3082
- if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { durationHours: original.durationHours });
3227
+ if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { endDate: original.endDate });
3083
3228
  }
3084
3229
  }
3085
3230
  this.#dragOriginals.clear();
@@ -3183,9 +3328,10 @@ var GanttChart = class {
3183
3328
  */
3184
3329
  update(newInput) {
3185
3330
  this.#assertAlive();
3186
- validateLinkRefs(newInput.tasks, newInput.links);
3187
- detectCycles(newInput.tasks, newInput.links);
3188
- this.#input = structuredClone(newInput);
3331
+ const input = newInput;
3332
+ validateLinkRefs(input.tasks, input.links);
3333
+ detectCycles(input.tasks, input.links);
3334
+ this.#input = structuredClone(input);
3189
3335
  this.#taskIndex = buildTaskIndex(this.#input.tasks);
3190
3336
  this.#expandedIds = getInitialExpandedIds(this.#input.tasks);
3191
3337
  if (this.#rafPending && this.#rafId !== null) {
@@ -3236,9 +3382,10 @@ var GanttChart = class {
3236
3382
  if (opts.weekendDays !== void 0) this.#weekendDays = normalizeWeekendDays(opts.weekendDays);
3237
3383
  if (opts.specialDays !== void 0) this.#specialDaysByDate = buildSpecialDayIndex(opts.specialDays);
3238
3384
  if (opts.theme !== void 0) this.#applyTheme();
3385
+ if (opts.showAddTaskButton !== void 0) this.#showAddTaskButton = opts.showAddTaskButton;
3239
3386
  const hasLayoutChange = opts.leftPaneWidth !== void 0 || opts.responsiveSplitPane !== void 0 || opts.mobileBreakpoint !== void 0 || opts.mobileLeftPaneMinWidth !== void 0 || opts.mobileLeftPaneMaxRatio !== void 0 || opts.timelineMinWidth !== void 0;
3240
3387
  if (hasLayoutChange) this.#applyResponsivePaneStyles();
3241
- const hasLeftPaneChange = columnsChanged || opts.locale !== void 0;
3388
+ const hasLeftPaneChange = columnsChanged || opts.locale !== void 0 || opts.showAddTaskButton !== void 0;
3242
3389
  const hasRightPaneChange = opts.scale !== void 0 || opts.showWeekends !== void 0 || opts.weekendDays !== void 0 || opts.specialDays !== void 0 || opts.highlightLinkedDependenciesOnSelect !== void 0 || opts.linkCreationEnabled !== void 0 || opts.progressDragEnabled !== void 0 || opts.viewportStart !== void 0 || opts.viewportEnd !== void 0 || opts.locale !== void 0 || opts.timelineMinWidth !== void 0;
3243
3390
  if (!(hasLeftPaneChange || hasRightPaneChange || hasLayoutChange)) return;
3244
3391
  if (this.#rafPending && this.#rafId !== null) {
@@ -3445,7 +3592,7 @@ var GanttChart = class {
3445
3592
  },
3446
3593
  onTaskDoubleClick: (payload) => this.#cbs.onTaskDoubleClick?.(payload),
3447
3594
  onTaskAdd: (id) => this.#cbs.onTaskAdd?.(id)
3448
- }, this.#columns);
3595
+ }, this.#columns, this.#showAddTaskButton);
3449
3596
  }
3450
3597
  #renderTimeline = () => {
3451
3598
  this.#rafPending = false;
@@ -3589,6 +3736,6 @@ var GanttChart = class {
3589
3736
  }
3590
3737
  };
3591
3738
  //#endregion
3592
- export { BAR_HEIGHT, BAR_Y_OFFSET, CHART_LOCALE_EN_US, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, GanttChart, GanttError, GanttInputSchema, LinkSchema, LinkTypeSchema, MILESTONE_HALF, MILESTONE_SIZE, ROW_HEIGHT, SCALE_CONFIGS, SpecialDayKindSchema, SpecialDaySchema, TaskKindSchema, TaskSchema, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, parseGanttInput, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
3739
+ export { BAR_HEIGHT, BAR_Y_OFFSET, CHART_LOCALE_DE_DE, CHART_LOCALE_EN_GB, CHART_LOCALE_EN_US, CHART_LOCALE_ES_ES, CHART_LOCALE_FR_FR, CHART_LOCALE_IT_IT, CHART_LOCALE_JA_JP, CHART_LOCALE_PT_PT, CHART_LOCALE_ZH_CN, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, GanttChart, GanttError, GanttInputSchema, LinkSchema, LinkTypeSchema, MILESTONE_HALF, MILESTONE_SIZE, ROW_HEIGHT, SCALE_CONFIGS, SpecialDayKindSchema, SpecialDaySchema, TaskKindSchema, TaskSchema, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, parseGanttInput, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
3593
3740
 
3594
3741
  //# sourceMappingURL=index.mjs.map