gantt-renderer 0.9.0 → 0.11.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.d.mts +35 -7
  3. package/dist/index.mjs +136 -9
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/{locale-BQKHOEOx.d.mts → locale-B81kcgPd.d.mts} +2 -2
  6. package/dist/locales/all.d.mts +1 -1
  7. package/dist/locales/ar.d.mts +1 -1
  8. package/dist/locales/ar.mjs +2 -0
  9. package/dist/locales/ar.mjs.map +1 -1
  10. package/dist/locales/be.d.mts +1 -1
  11. package/dist/locales/be.mjs +2 -0
  12. package/dist/locales/be.mjs.map +1 -1
  13. package/dist/locales/bg.d.mts +1 -1
  14. package/dist/locales/bg.mjs +2 -0
  15. package/dist/locales/bg.mjs.map +1 -1
  16. package/dist/locales/ca.d.mts +1 -1
  17. package/dist/locales/ca.mjs +2 -0
  18. package/dist/locales/ca.mjs.map +1 -1
  19. package/dist/locales/cs.d.mts +1 -1
  20. package/dist/locales/cs.mjs +2 -0
  21. package/dist/locales/cs.mjs.map +1 -1
  22. package/dist/locales/cy.d.mts +1 -1
  23. package/dist/locales/cy.mjs +2 -0
  24. package/dist/locales/cy.mjs.map +1 -1
  25. package/dist/locales/da.d.mts +1 -1
  26. package/dist/locales/da.mjs +2 -0
  27. package/dist/locales/da.mjs.map +1 -1
  28. package/dist/locales/de.d.mts +1 -1
  29. package/dist/locales/de.mjs +2 -0
  30. package/dist/locales/de.mjs.map +1 -1
  31. package/dist/locales/el.d.mts +1 -1
  32. package/dist/locales/el.mjs +2 -0
  33. package/dist/locales/el.mjs.map +1 -1
  34. package/dist/locales/en.d.mts +1 -1
  35. package/dist/locales/en.mjs +2 -0
  36. package/dist/locales/en.mjs.map +1 -1
  37. package/dist/locales/es.d.mts +1 -1
  38. package/dist/locales/es.mjs +2 -0
  39. package/dist/locales/es.mjs.map +1 -1
  40. package/dist/locales/et.d.mts +1 -1
  41. package/dist/locales/et.mjs +2 -0
  42. package/dist/locales/et.mjs.map +1 -1
  43. package/dist/locales/eu.d.mts +1 -1
  44. package/dist/locales/eu.mjs +2 -0
  45. package/dist/locales/eu.mjs.map +1 -1
  46. package/dist/locales/fi.d.mts +1 -1
  47. package/dist/locales/fi.mjs +2 -0
  48. package/dist/locales/fi.mjs.map +1 -1
  49. package/dist/locales/fr.d.mts +1 -1
  50. package/dist/locales/fr.mjs +2 -0
  51. package/dist/locales/fr.mjs.map +1 -1
  52. package/dist/locales/ga.d.mts +1 -1
  53. package/dist/locales/ga.mjs +2 -0
  54. package/dist/locales/ga.mjs.map +1 -1
  55. package/dist/locales/hi.d.mts +1 -1
  56. package/dist/locales/hi.mjs +2 -0
  57. package/dist/locales/hi.mjs.map +1 -1
  58. package/dist/locales/hr.d.mts +1 -1
  59. package/dist/locales/hr.mjs +2 -0
  60. package/dist/locales/hr.mjs.map +1 -1
  61. package/dist/locales/hu.d.mts +1 -1
  62. package/dist/locales/hu.mjs +2 -0
  63. package/dist/locales/hu.mjs.map +1 -1
  64. package/dist/locales/id.d.mts +1 -1
  65. package/dist/locales/id.mjs +2 -0
  66. package/dist/locales/id.mjs.map +1 -1
  67. package/dist/locales/it.d.mts +1 -1
  68. package/dist/locales/it.mjs +2 -0
  69. package/dist/locales/it.mjs.map +1 -1
  70. package/dist/locales/ja.d.mts +1 -1
  71. package/dist/locales/ja.mjs +2 -0
  72. package/dist/locales/ja.mjs.map +1 -1
  73. package/dist/locales/ko.d.mts +1 -1
  74. package/dist/locales/ko.mjs +2 -0
  75. package/dist/locales/ko.mjs.map +1 -1
  76. package/dist/locales/load.d.mts +1 -1
  77. package/dist/locales/lt.d.mts +1 -1
  78. package/dist/locales/lt.mjs +2 -0
  79. package/dist/locales/lt.mjs.map +1 -1
  80. package/dist/locales/lv.d.mts +1 -1
  81. package/dist/locales/lv.mjs +2 -0
  82. package/dist/locales/lv.mjs.map +1 -1
  83. package/dist/locales/mk.d.mts +1 -1
  84. package/dist/locales/mk.mjs +2 -0
  85. package/dist/locales/mk.mjs.map +1 -1
  86. package/dist/locales/mt.d.mts +1 -1
  87. package/dist/locales/mt.mjs +2 -0
  88. package/dist/locales/mt.mjs.map +1 -1
  89. package/dist/locales/nb.d.mts +1 -1
  90. package/dist/locales/nb.mjs +2 -0
  91. package/dist/locales/nb.mjs.map +1 -1
  92. package/dist/locales/nl.d.mts +1 -1
  93. package/dist/locales/nl.mjs +2 -0
  94. package/dist/locales/nl.mjs.map +1 -1
  95. package/dist/locales/pl.d.mts +1 -1
  96. package/dist/locales/pl.mjs +2 -0
  97. package/dist/locales/pl.mjs.map +1 -1
  98. package/dist/locales/pt-BR.d.mts +1 -1
  99. package/dist/locales/pt-BR.mjs +2 -0
  100. package/dist/locales/pt-BR.mjs.map +1 -1
  101. package/dist/locales/pt-PT.d.mts +1 -1
  102. package/dist/locales/pt-PT.mjs +2 -0
  103. package/dist/locales/pt-PT.mjs.map +1 -1
  104. package/dist/locales/registry.d.mts +1 -1
  105. package/dist/locales/ro.d.mts +1 -1
  106. package/dist/locales/ro.mjs +2 -0
  107. package/dist/locales/ro.mjs.map +1 -1
  108. package/dist/locales/ru.d.mts +1 -1
  109. package/dist/locales/ru.mjs +2 -0
  110. package/dist/locales/ru.mjs.map +1 -1
  111. package/dist/locales/sk.d.mts +1 -1
  112. package/dist/locales/sk.mjs +2 -0
  113. package/dist/locales/sk.mjs.map +1 -1
  114. package/dist/locales/sl.d.mts +1 -1
  115. package/dist/locales/sl.mjs +2 -0
  116. package/dist/locales/sl.mjs.map +1 -1
  117. package/dist/locales/sq.d.mts +1 -1
  118. package/dist/locales/sq.mjs +2 -0
  119. package/dist/locales/sq.mjs.map +1 -1
  120. package/dist/locales/sr.d.mts +1 -1
  121. package/dist/locales/sr.mjs +2 -0
  122. package/dist/locales/sr.mjs.map +1 -1
  123. package/dist/locales/sv.d.mts +1 -1
  124. package/dist/locales/sv.mjs +2 -0
  125. package/dist/locales/sv.mjs.map +1 -1
  126. package/dist/locales/th.d.mts +1 -1
  127. package/dist/locales/th.mjs +2 -0
  128. package/dist/locales/th.mjs.map +1 -1
  129. package/dist/locales/tr.d.mts +1 -1
  130. package/dist/locales/tr.mjs +2 -0
  131. package/dist/locales/tr.mjs.map +1 -1
  132. package/dist/locales/uk.d.mts +1 -1
  133. package/dist/locales/uk.mjs +2 -0
  134. package/dist/locales/uk.mjs.map +1 -1
  135. package/dist/locales/zh-Hans.d.mts +1 -1
  136. package/dist/locales/zh-Hans.mjs +2 -0
  137. package/dist/locales/zh-Hans.mjs.map +1 -1
  138. package/dist/locales/zh-Hant.d.mts +1 -1
  139. package/dist/locales/zh-Hant.mjs +2 -0
  140. package/dist/locales/zh-Hant.mjs.map +1 -1
  141. package/dist/styles/gantt.css +23 -0
  142. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # [0.11.0](https://github.com/doberkofler/gantt-renderer/compare/v0.10.0...v0.11.0) (2026-05-13)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **gantt:** default fireCallback to false on select, collapseAll, and expandAll ([2572d4c](https://github.com/doberkofler/gantt-renderer/commit/2572d4c0c75b672b5f2d98d9a1dfde5fb30a421d))
7
+
8
+
9
+ ### Features
10
+
11
+ * **gantt:** add getOpenStates instance method ([8c8b09c](https://github.com/doberkofler/gantt-renderer/commit/8c8b09cf323b45cc237e969ea190fb7cd7cb5428))
12
+
13
+ # [0.10.0](https://github.com/doberkofler/gantt-renderer/compare/v0.9.0...v0.10.0) (2026-05-13)
14
+
15
+
16
+ ### Features
17
+
18
+ * **gantt:** add onExpandCollapse/onExpandCollapseAll callbacks and header +/− controls ([652d626](https://github.com/doberkofler/gantt-renderer/commit/652d6261b7d6cdfd74c07acebb54b84708a155b1))
19
+
1
20
  # [0.9.0](https://github.com/doberkofler/gantt-renderer/compare/v0.8.0...v0.9.0) (2026-05-13)
2
21
 
3
22
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as deriveWeekStartsOn, c as formatWeekNumber, i as deriveWeekNumbering, l as resolveChartLocale, n as EN_US_LABELS, o as deriveWeekendDays, r as LocaleLabelKey, s as formatLabel, t as ChartLocale } from "./locale-BQKHOEOx.mjs";
1
+ import { a as deriveWeekStartsOn, c as formatWeekNumber, i as deriveWeekNumbering, l as resolveChartLocale, n as EN_US_LABELS, o as deriveWeekendDays, r as LocaleLabelKey, s as formatLabel, t as ChartLocale } from "./locale-B81kcgPd.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/lib/validation/schemas.d.ts
@@ -489,6 +489,14 @@ type OnProgressChange<TTaskData = never, TLinkData = never> = (payload: {
489
489
  newPercentComplete: number;
490
490
  instance: GanttInstance<TTaskData, TLinkData>;
491
491
  }) => boolean | Promise<boolean>;
492
+ type OnExpandCollapse<TTaskData = never, TLinkData = never> = (payload: {
493
+ task: Task<TTaskData>;
494
+ instance: GanttInstance<TTaskData, TLinkData>;
495
+ }) => void | Promise<void>;
496
+ type OnExpandCollapseAll<TTaskData = never, TLinkData = never> = (payload: {
497
+ tasks: Task<TTaskData>[];
498
+ instance: GanttInstance<TTaskData, TLinkData>;
499
+ }) => void | Promise<void>;
492
500
  type OnTooltipText<TTaskData = never, TLinkData = never> = (payload: {
493
501
  task: Task<TTaskData>;
494
502
  instance: GanttInstance<TTaskData, TLinkData>;
@@ -503,6 +511,8 @@ type GanttCallbacks<TTaskData = never, TLinkData = never> = {
503
511
  onLinkClick?: OnLinkClick<TTaskData, TLinkData>;
504
512
  onLinkDblClick?: OnLinkDblClick<TTaskData, TLinkData>;
505
513
  onProgressChange?: OnProgressChange<TTaskData, TLinkData>;
514
+ onExpandCollapse?: OnExpandCollapse<TTaskData, TLinkData>;
515
+ onExpandCollapseAll?: OnExpandCollapseAll<TTaskData, TLinkData>;
506
516
  onTooltipText?: OnTooltipText<TTaskData, TLinkData>;
507
517
  onLeftPaneWidthChange?: (payload: {
508
518
  width: number;
@@ -542,8 +552,12 @@ type GanttInstance<TTaskData = never, TLinkData = never> = {
542
552
  setOptions: (opts: Partial<GanttOptions>) => void;
543
553
  setCallbacks: (cbs: GanttCallbacks<TTaskData, TLinkData>) => void;
544
554
  select: (id: number | null, fireCallback?: boolean) => void;
545
- collapseAll: () => void;
546
- expandAll: () => void;
555
+ collapseAll: (fireCallback?: boolean) => void;
556
+ expandAll: (fireCallback?: boolean) => void;
557
+ getOpenStates: () => {
558
+ id: number;
559
+ open: boolean;
560
+ }[];
547
561
  destroy: () => void;
548
562
  };
549
563
  /**
@@ -602,22 +616,36 @@ declare class GanttChart<TTaskData = never, TLinkData = never> implements GanttI
602
616
  * Programmatically selects or deselects a task.
603
617
  *
604
618
  * @param id - The task ID to select, or `null` to clear the selection.
605
- * @param fireCallback - Whether to fire the `onTaskClick` callback. Default `true`.
619
+ * @param fireCallback - Whether to fire the `onTaskClick` callback. Default `false`.
606
620
  * @throws {GanttError} When the instance has been destroyed.
607
621
  */
608
622
  select(id: number | null, fireCallback?: boolean): void;
609
623
  /**
610
624
  * Collapses all expandable groups in the task tree.
611
625
  *
626
+ * @param fireCallback - Whether to fire the `onExpandCollapseAll` callback. Default `false`.
612
627
  * @throws {GanttError} When the instance has been destroyed.
613
628
  */
614
- collapseAll(): void;
629
+ collapseAll(fireCallback?: boolean): void;
615
630
  /**
616
631
  * Expands all expandable groups in the task tree.
617
632
  *
633
+ * @param fireCallback - Whether to fire the `onExpandCollapseAll` callback. Default `false`.
634
+ * @throws {GanttError} When the instance has been destroyed.
635
+ */
636
+ expandAll(fireCallback?: boolean): void;
637
+ /**
638
+ * Returns the current expand/collapse state of every expandable node
639
+ * (project tasks that have children). The result is ordered by the
640
+ * input task list order.
641
+ *
642
+ * @returns An array of `{id, open}` objects for expandable nodes only.
618
643
  * @throws {GanttError} When the instance has been destroyed.
619
644
  */
620
- expandAll(): void;
645
+ getOpenStates(): {
646
+ id: number;
647
+ open: boolean;
648
+ }[];
621
649
  /**
622
650
  * Removes the chart DOM and internal listeners, rendering the instance
623
651
  * unusable. Subsequent calls to any public method will throw.
@@ -639,5 +667,5 @@ declare class GanttError extends Error {
639
667
  constructor(code: GanttErrorCode, message: string);
640
668
  }
641
669
  //#endregion
642
- export { BAR_HEIGHT, BAR_Y_OFFSET, type BarLayout, type ChartLocale, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, type GanttCallbacks, GanttChart, GanttError, type GanttErrorCode, type GanttInput, type GanttInputRaw, type GanttInstance, type GanttOptions, type GridColumn, type Link, type LinkType, type LocaleLabelKey, MILESTONE_HALF, MILESTONE_SIZE, type OnLinkClick, type OnLinkCreate, type OnLinkDblClick, type OnProgressChange, type OnTaskAdd, type OnTaskClick, type OnTaskDoubleClick, type OnTaskMove, type OnTaskResize, type OnTooltipText, type PixelMapper, type Point, ROW_HEIGHT, type RoutedLink, SCALE_CONFIGS, type ScaleConfig, type SpecialDay, type SpecialDayKind, type Task, type TaskDataField, type TaskKind, type TaskNode, type ThemeMode, type TimeScale, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
670
+ export { BAR_HEIGHT, BAR_Y_OFFSET, type BarLayout, type ChartLocale, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, type GanttCallbacks, GanttChart, GanttError, type GanttErrorCode, type GanttInput, type GanttInputRaw, type GanttInstance, type GanttOptions, type GridColumn, type Link, type LinkType, type LocaleLabelKey, MILESTONE_HALF, MILESTONE_SIZE, type OnExpandCollapse, type OnExpandCollapseAll, type OnLinkClick, type OnLinkCreate, type OnLinkDblClick, type OnProgressChange, type OnTaskAdd, type OnTaskClick, type OnTaskDoubleClick, type OnTaskMove, type OnTaskResize, type OnTooltipText, type PixelMapper, type Point, ROW_HEIGHT, type RoutedLink, SCALE_CONFIGS, type ScaleConfig, type SpecialDay, type SpecialDayKind, type Task, type TaskDataField, type TaskKind, type TaskNode, type ThemeMode, type TimeScale, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
643
671
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -303,6 +303,8 @@ const EN_US_LABELS = {
303
303
  ariaTask: "Task {0}",
304
304
  ariaMilestone: "Milestone {0}",
305
305
  addSubtaskTitle: "Add subtask",
306
+ expandAllTitle: "Expand all",
307
+ collapseAllTitle: "Collapse all",
306
308
  columnTaskName: "Task name",
307
309
  columnStartDate: "Start",
308
310
  columnEndDate: "End",
@@ -1784,9 +1786,10 @@ function renderLeftPane(container, state, cbs, columns, showAddTaskButton = true
1784
1786
  * Builds the header row for the left pane.
1785
1787
  *
1786
1788
  * @param columns - The grid column schema.
1789
+ * @param locale - The current chart locale.
1787
1790
  * @returns The header DOM element.
1788
1791
  */
1789
- function buildLeftPaneHeader(columns) {
1792
+ function buildLeftPaneHeader(columns, locale) {
1790
1793
  const header = el("div");
1791
1794
  css(header, {
1792
1795
  display: "grid",
@@ -1809,6 +1812,60 @@ function buildLeftPaneHeader(columns) {
1809
1812
  display: "flex",
1810
1813
  alignItems: "flex-end"
1811
1814
  });
1815
+ if (i === 0 && visible[0]?.id === "name") {
1816
+ const btnContainer = el("div");
1817
+ btnContainer.className = "gantt-header-tree-controls";
1818
+ css(btnContainer, {
1819
+ display: "flex",
1820
+ gap: "4px",
1821
+ marginRight: "6px",
1822
+ paddingBottom: "1px"
1823
+ });
1824
+ const expandBtn = el("button");
1825
+ expandBtn.className = "gantt-header-expand-btn";
1826
+ expandBtn.textContent = "+";
1827
+ expandBtn.title = locale.labels?.expandAllTitle ?? EN_US_LABELS.expandAllTitle;
1828
+ expandBtn.setAttribute("aria-label", expandBtn.title);
1829
+ css(expandBtn, {
1830
+ width: "18px",
1831
+ height: "18px",
1832
+ display: "flex",
1833
+ alignItems: "center",
1834
+ justifyContent: "center",
1835
+ background: "var(--gantt-header-bg)",
1836
+ border: "1px solid var(--gantt-border)",
1837
+ borderRadius: "3px",
1838
+ cursor: "pointer",
1839
+ color: "var(--gantt-text-secondary)",
1840
+ fontSize: "14px",
1841
+ fontWeight: "var(--gantt-font-weight-bold)",
1842
+ lineHeight: "1",
1843
+ padding: "0"
1844
+ });
1845
+ const collapseBtn = el("button");
1846
+ collapseBtn.className = "gantt-header-collapse-btn";
1847
+ collapseBtn.textContent = "−";
1848
+ collapseBtn.title = locale.labels?.collapseAllTitle ?? EN_US_LABELS.collapseAllTitle;
1849
+ collapseBtn.setAttribute("aria-label", collapseBtn.title);
1850
+ css(collapseBtn, {
1851
+ width: "18px",
1852
+ height: "18px",
1853
+ display: "flex",
1854
+ alignItems: "center",
1855
+ justifyContent: "center",
1856
+ background: "var(--gantt-header-bg)",
1857
+ border: "1px solid var(--gantt-border)",
1858
+ borderRadius: "3px",
1859
+ cursor: "pointer",
1860
+ color: "var(--gantt-text-secondary)",
1861
+ fontSize: "14px",
1862
+ fontWeight: "var(--gantt-font-weight-bold)",
1863
+ lineHeight: "1",
1864
+ padding: "0"
1865
+ });
1866
+ btnContainer.append(expandBtn, collapseBtn);
1867
+ wrapper.append(btnContainer);
1868
+ }
1812
1869
  const cell = el("span");
1813
1870
  css(cell, {
1814
1871
  fontSize: "var(--gantt-font-size-xs)",
@@ -3259,10 +3316,10 @@ var GanttChart = class {
3259
3316
  * Programmatically selects or deselects a task.
3260
3317
  *
3261
3318
  * @param id - The task ID to select, or `null` to clear the selection.
3262
- * @param fireCallback - Whether to fire the `onTaskClick` callback. Default `true`.
3319
+ * @param fireCallback - Whether to fire the `onTaskClick` callback. Default `false`.
3263
3320
  * @throws {GanttError} When the instance has been destroyed.
3264
3321
  */
3265
- select(id, fireCallback = true) {
3322
+ select(id, fireCallback = false) {
3266
3323
  this.#assertAlive();
3267
3324
  if (id === null) this.#selectedId = null;
3268
3325
  else {
@@ -3283,10 +3340,12 @@ var GanttChart = class {
3283
3340
  /**
3284
3341
  * Collapses all expandable groups in the task tree.
3285
3342
  *
3343
+ * @param fireCallback - Whether to fire the `onExpandCollapseAll` callback. Default `false`.
3286
3344
  * @throws {GanttError} When the instance has been destroyed.
3287
3345
  */
3288
- collapseAll() {
3346
+ collapseAll(fireCallback = false) {
3289
3347
  this.#assertAlive();
3348
+ const changed = fireCallback ? this.#buildExpandCollapseAllPayload(false) : [];
3290
3349
  this.#expandedIds.clear();
3291
3350
  if (this.#rafPending && this.#rafId !== null) {
3292
3351
  cancelAnimationFrame(this.#rafId);
@@ -3294,14 +3353,20 @@ var GanttChart = class {
3294
3353
  this.#rafPending = false;
3295
3354
  }
3296
3355
  this.#render();
3356
+ if (changed.length > 0) this.#callbacks.onExpandCollapseAll?.({
3357
+ tasks: changed,
3358
+ instance: this
3359
+ });
3297
3360
  }
3298
3361
  /**
3299
3362
  * Expands all expandable groups in the task tree.
3300
3363
  *
3364
+ * @param fireCallback - Whether to fire the `onExpandCollapseAll` callback. Default `false`.
3301
3365
  * @throws {GanttError} When the instance has been destroyed.
3302
3366
  */
3303
- expandAll() {
3367
+ expandAll(fireCallback = false) {
3304
3368
  this.#assertAlive();
3369
+ const changed = fireCallback ? this.#buildExpandCollapseAllPayload(true) : [];
3305
3370
  this.#expandedIds.clear();
3306
3371
  if (this.#input !== null) for (const id of getExpandableTaskIds(this.#input.tasks)) this.#expandedIds.add(id);
3307
3372
  if (this.#rafPending && this.#rafId !== null) {
@@ -3310,6 +3375,42 @@ var GanttChart = class {
3310
3375
  this.#rafPending = false;
3311
3376
  }
3312
3377
  this.#render();
3378
+ if (changed.length > 0) this.#callbacks.onExpandCollapseAll?.({
3379
+ tasks: changed,
3380
+ instance: this
3381
+ });
3382
+ }
3383
+ #buildExpandCollapseAllPayload(open) {
3384
+ if (this.#input === null) return [];
3385
+ const expandableIds = getExpandableTaskIds(this.#input.tasks);
3386
+ const changed = [];
3387
+ for (const id of expandableIds) if (this.#expandedIds.has(id) !== open) {
3388
+ const task = this.#findTask(id);
3389
+ if (task !== void 0) changed.push(task.kind === "project" ? {
3390
+ ...task,
3391
+ open
3392
+ } : { ...task });
3393
+ }
3394
+ return changed;
3395
+ }
3396
+ /**
3397
+ * Returns the current expand/collapse state of every expandable node
3398
+ * (project tasks that have children). The result is ordered by the
3399
+ * input task list order.
3400
+ *
3401
+ * @returns An array of `{id, open}` objects for expandable nodes only.
3402
+ * @throws {GanttError} When the instance has been destroyed.
3403
+ */
3404
+ getOpenStates() {
3405
+ this.#assertAlive();
3406
+ if (this.#input === null) return [];
3407
+ const expandableIds = getExpandableTaskIds(this.#input.tasks);
3408
+ const result = [];
3409
+ for (const task of this.#input.tasks) if (expandableIds.has(task.id)) result.push({
3410
+ id: task.id,
3411
+ open: this.#expandedIds.has(task.id)
3412
+ });
3413
+ return result;
3313
3414
  }
3314
3415
  /**
3315
3416
  * Removes the chart DOM and internal listeners, rendering the instance
@@ -3446,8 +3547,20 @@ var GanttChart = class {
3446
3547
  #renderGridInternal(state) {
3447
3548
  renderLeftPane(this.#leftBody, state, {
3448
3549
  onToggle: (id) => {
3449
- if (this.#expandedIds.has(id)) this.#expandedIds.delete(id);
3450
- else this.#expandedIds.add(id);
3550
+ const expanded = !this.#expandedIds.has(id);
3551
+ if (expanded) this.#expandedIds.add(id);
3552
+ else this.#expandedIds.delete(id);
3553
+ const task = this.#findTask(id);
3554
+ if (task !== void 0) {
3555
+ const payload = task.kind === "project" ? {
3556
+ ...task,
3557
+ open: expanded
3558
+ } : { ...task };
3559
+ this.#callbacks.onExpandCollapse?.({
3560
+ task: payload,
3561
+ instance: this
3562
+ });
3563
+ }
3451
3564
  this.#scheduleRender();
3452
3565
  },
3453
3566
  onTaskClick: (id) => this.#cbs.onTaskClick?.(id),
@@ -3469,12 +3582,25 @@ var GanttChart = class {
3469
3582
  #rebuildLeftPaneHeader() {
3470
3583
  this.#columnResizeCleanup();
3471
3584
  clearChildren(this.#leftHeader);
3472
- const headerEl = buildLeftPaneHeader(this.#columns);
3585
+ const headerEl = buildLeftPaneHeader(this.#columns, this.#locale);
3586
+ this.#wireHeaderTreeControls(headerEl);
3473
3587
  this.#leftHeader.append(headerEl);
3474
3588
  this.#columnResizeCleanup = setupColumnResize(headerEl, this.#leftBody, this.#columns, (updated) => {
3475
3589
  this.#cbs.onGridColumnsChange?.(updated);
3476
3590
  });
3477
3591
  }
3592
+ #wireHeaderTreeControls(headerEl) {
3593
+ const expandBtn = headerEl.querySelector(".gantt-header-expand-btn");
3594
+ const collapseBtn = headerEl.querySelector(".gantt-header-collapse-btn");
3595
+ if (expandBtn !== null) expandBtn.addEventListener("click", (e) => {
3596
+ e.stopPropagation();
3597
+ this.expandAll(true);
3598
+ });
3599
+ if (collapseBtn !== null) collapseBtn.addEventListener("click", (e) => {
3600
+ e.stopPropagation();
3601
+ this.collapseAll(true);
3602
+ });
3603
+ }
3478
3604
  #scheduleRender() {
3479
3605
  if (this.#rafPending || this.#destroyed) return;
3480
3606
  this.#rafPending = true;
@@ -3527,7 +3653,8 @@ var GanttChart = class {
3527
3653
  zIndex: "11",
3528
3654
  background: "var(--gantt-header-bg)"
3529
3655
  });
3530
- const headerEl = buildLeftPaneHeader(this.#columns);
3656
+ const headerEl = buildLeftPaneHeader(this.#columns, this.#locale);
3657
+ this.#wireHeaderTreeControls(headerEl);
3531
3658
  leftHeader.append(headerEl);
3532
3659
  leftPane.append(leftHeader);
3533
3660
  this.#leftHeader = leftHeader;