@dou-ui/gantt 0.0.0-stage → 0.1.0-alpha.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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/collaboration/index.js +323 -0
  4. package/dist/collaboration/index.js.map +1 -0
  5. package/dist/collaboration/peers.js +78 -0
  6. package/dist/collaboration/peers.js.map +1 -0
  7. package/dist/components/cell-editor.js +6 -0
  8. package/dist/components/cell-editor.js.map +1 -0
  9. package/dist/components/cell-editor.vue_vue_type_script_setup_true_lang.js +114 -0
  10. package/dist/components/cell-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/components/gantt-menu.js +6 -0
  12. package/dist/components/gantt-menu.js.map +1 -0
  13. package/dist/components/gantt-menu.vue_vue_type_script_setup_true_lang.js +40 -0
  14. package/dist/components/gantt-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  15. package/dist/components/time-grid.js +6 -0
  16. package/dist/components/time-grid.js.map +1 -0
  17. package/dist/components/time-grid.vue_vue_type_script_setup_true_lang.js +32 -0
  18. package/dist/components/time-grid.vue_vue_type_script_setup_true_lang.js.map +1 -0
  19. package/dist/components/time-header.js +6 -0
  20. package/dist/components/time-header.js.map +1 -0
  21. package/dist/components/time-header.vue_vue_type_script_setup_true_lang.js +60 -0
  22. package/dist/components/time-header.vue_vue_type_script_setup_true_lang.js.map +1 -0
  23. package/dist/context.js +12 -0
  24. package/dist/context.js.map +1 -0
  25. package/dist/core/baseline.js +48 -0
  26. package/dist/core/baseline.js.map +1 -0
  27. package/dist/core/calendar.js +191 -0
  28. package/dist/core/calendar.js.map +1 -0
  29. package/dist/core/csv.js +424 -0
  30. package/dist/core/csv.js.map +1 -0
  31. package/dist/core/document.js +231 -0
  32. package/dist/core/document.js.map +1 -0
  33. package/dist/core/extension.js +42 -0
  34. package/dist/core/extension.js.map +1 -0
  35. package/dist/core/geometry.js +112 -0
  36. package/dist/core/geometry.js.map +1 -0
  37. package/dist/core/history.js +70 -0
  38. package/dist/core/history.js.map +1 -0
  39. package/dist/core/links.js +72 -0
  40. package/dist/core/links.js.map +1 -0
  41. package/dist/core/model.js +52 -0
  42. package/dist/core/model.js.map +1 -0
  43. package/dist/core/order.js +34 -0
  44. package/dist/core/order.js.map +1 -0
  45. package/dist/core/scale.js +118 -0
  46. package/dist/core/scale.js.map +1 -0
  47. package/dist/core/schedule.js +235 -0
  48. package/dist/core/schedule.js.map +1 -0
  49. package/dist/core/time.js +81 -0
  50. package/dist/core/time.js.map +1 -0
  51. package/dist/core/transaction.js +371 -0
  52. package/dist/core/transaction.js.map +1 -0
  53. package/dist/core/tree.js +114 -0
  54. package/dist/core/tree.js.map +1 -0
  55. package/dist/export/draw.js +214 -0
  56. package/dist/export/draw.js.map +1 -0
  57. package/dist/export/theme.js +131 -0
  58. package/dist/export/theme.js.map +1 -0
  59. package/dist/gantt.js +6 -0
  60. package/dist/gantt.js.map +1 -0
  61. package/dist/gantt.vue_vue_type_script_setup_true_lang.js +2774 -0
  62. package/dist/gantt.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/index.js +20 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/labels.js +87 -0
  66. package/dist/labels.js.map +1 -0
  67. package/dist/locale.js +355 -0
  68. package/dist/locale.js.map +1 -0
  69. package/dist/scheduler/index.js +8 -0
  70. package/dist/scheduler/index.js.map +1 -0
  71. package/dist/scheduler/layout.js +97 -0
  72. package/dist/scheduler/layout.js.map +1 -0
  73. package/dist/scheduler/scheduler.js +6 -0
  74. package/dist/scheduler/scheduler.js.map +1 -0
  75. package/dist/scheduler/scheduler.vue_vue_type_script_setup_true_lang.js +1359 -0
  76. package/dist/scheduler/scheduler.vue_vue_type_script_setup_true_lang.js.map +1 -0
  77. package/dist/scheduler/style.d.ts +1 -0
  78. package/dist/scheduler/style.js +2 -0
  79. package/dist/style.css +1 -0
  80. package/dist/style.d.ts +1 -0
  81. package/dist/style.js +4 -0
  82. package/dist/time-axis.js +337 -0
  83. package/dist/time-axis.js.map +1 -0
  84. package/dist/types/collaboration/index.d.ts +39 -0
  85. package/dist/types/collaboration/peers.d.ts +22 -0
  86. package/dist/types/components/cell-editor.vue.d.ts +21 -0
  87. package/dist/types/components/gantt-menu.vue.d.ts +18 -0
  88. package/dist/types/components/time-grid.vue.d.ts +12 -0
  89. package/dist/types/components/time-header.vue.d.ts +23 -0
  90. package/dist/types/context.d.ts +64 -0
  91. package/dist/types/core/baseline.d.ts +14 -0
  92. package/dist/types/core/calendar.d.ts +75 -0
  93. package/dist/types/core/csv.d.ts +77 -0
  94. package/dist/types/core/document.d.ts +51 -0
  95. package/dist/types/core/extension.d.ts +88 -0
  96. package/dist/types/core/geometry.d.ts +33 -0
  97. package/dist/types/core/history.d.ts +19 -0
  98. package/dist/types/core/links.d.ts +20 -0
  99. package/dist/types/core/menu.d.ts +27 -0
  100. package/dist/types/core/model.d.ts +72 -0
  101. package/dist/types/core/order.d.ts +9 -0
  102. package/dist/types/core/scale.d.ts +33 -0
  103. package/dist/types/core/schedule.d.ts +44 -0
  104. package/dist/types/core/time.d.ts +39 -0
  105. package/dist/types/core/transaction.d.ts +134 -0
  106. package/dist/types/core/tree.d.ts +30 -0
  107. package/dist/types/export/draw.d.ts +76 -0
  108. package/dist/types/export/theme.d.ts +49 -0
  109. package/dist/types/gantt.vue.d.ts +54 -0
  110. package/dist/types/index.d.ts +148 -0
  111. package/dist/types/labels.d.ts +24 -0
  112. package/dist/types/locale.d.ts +161 -0
  113. package/dist/types/scheduler/index.d.ts +102 -0
  114. package/dist/types/scheduler/layout.d.ts +58 -0
  115. package/dist/types/scheduler/scheduler.vue.d.ts +46 -0
  116. package/dist/types/scheduler/types.d.ts +109 -0
  117. package/dist/types/time-axis.d.ts +80 -0
  118. package/dist/types/types.d.ts +305 -0
  119. package/package.json +79 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-axis.js","names":[],"sources":["../src/time-axis.ts"],"sourcesContent":["import { createScrollLink, scrollPositionOf, useNow, useTouchScroll } from 'dou-ui'\nimport { computed, onMounted, ref, watch, type ComputedRef, type Ref, type ShallowRef } from 'vue'\nimport { offTime, type WorkCalendar } from './core/calendar'\nimport {\n DAY_WIDTH_RANGE,\n hourStep,\n levelByName,\n levelOf,\n unitStart,\n unitsBetween,\n type GanttScale,\n type TimeUnit,\n} from './core/scale'\nimport { MINUTES_PER_DAY, dayStart, parseTime, wallClockNow } from './core/time'\nimport { unitLabel } from './labels'\nimport type { GanttMessages } from './locale'\n\n/** Room around the visible part that is rendered too, in px. */\nconst OVERSCAN_X = 320\n/** The widest a day gets when fitting, so short plans don't zoom to hours. */\nconst FIT_MAX = 64\nconst ZOOM_STEP = 1.5\n/**\n * Room kept before the view on a touch screen, in screens and at least in\n * px, but no more time than `ROOM_BEFORE_TIME`. Time can only be added there\n * once a touch scroll is over, so it is enough for a long run of fast swipes\n * that never lets the momentum stop. Only what is in view is drawn, so room\n * costs nothing but scroll width.\n */\nconst ROOM_BEFORE = 300\nconst ROOM_BEFORE_MIN = 400_000\n/** Ten thousand years, in minutes: room for a run of swipes at the widest scale too. */\nconst ROOM_BEFORE_TIME = 10_000 * 365 * MINUTES_PER_DAY\n/** The earliest the timeline goes, some 190,000 years back, well within what a date can be. */\nconst EARLIEST = -1e11\n/**\n * Room kept before the view with a mouse or trackpad, in screens. It is\n * filled as soon as the view comes near the start, and stays small so the\n * scrollbar thumb still shows where the view is.\n */\nconst ROOM_BEFORE_POINTER = 4\n/** Room kept after the view, in screens; time is added there as you scroll. */\nconst ROOM_AFTER = 4\n\nfunction fingers(event: TouchEvent) {\n const [a, b] = [event.touches[0]!, event.touches[1]!]\n return { a, b, distance: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY) }\n}\n\nexport interface HeaderCell {\n key: number\n end: number\n left: number\n width: number\n label: string\n}\n\nexport interface TimeAxisOptions {\n /** The scrolling timeline. */\n chart: Readonly<ShallowRef<HTMLElement | null>>\n /** The table body, kept at the same vertical scroll. */\n tableBody: Readonly<ShallowRef<HTMLElement | null>>\n viewWidth: Ref<number>\n scale: Ref<GanttScale | undefined>\n range: () => [string, string] | undefined\n today: () => boolean\n weekStart: ComputedRef<0 | 1>\n calendar: ComputedRef<WorkCalendar>\n messages: ComputedRef<GanttMessages>\n /** The time the content covers, or `null` when nothing is scheduled. */\n content: ComputedRef<{ start: number; end: number } | null>\n /** The width the timeline will have once laid out, for fitting. */\n fitWidth: () => number\n}\n\n/**\n * The time axis the gantt chart and the scheduler share: zoom, the extent\n * that grows as you scroll, the header cells, days off and the today line.\n */\nexport function useTimeAxis(o: TimeAxisOptions) {\n /** How wide a day is, in px; zooming changes it smoothly. */\n const dayWidth = ref(levelByName(o.scale.value ?? 'day').base)\n /** Without a `scale`, the first view picks the day width that fits the content. */\n let fitPending = o.scale.value == null\n const level = computed(() => levelOf(dayWidth.value))\n /** px per minute. */\n const ppm = computed(() => dayWidth.value / MINUTES_PER_DAY)\n\n watch(level, (next) => {\n if (o.scale.value !== next.name) o.scale.value = next.name\n })\n // a picked level zooms around now when it is in view, else around the middle\n watch(o.scale, (name) => {\n if (!name || level.value.name === name) return\n const x = todayX.value == null ? null : todayX.value - scrollLeft.value\n zoomTo(levelByName(name).base, x != null && x >= 0 && x <= o.viewWidth.value ? x : undefined)\n })\n\n const nowMs = useNow({ interval: 60_000 })\n const now = computed(() => wallClockNow(new Date(nowMs.value)))\n\n /** The content's time, or the coming month when nothing is scheduled. */\n const content = computed(() => {\n const known = o.content.value\n if (known) return known\n const start = dayStart(now.value)\n return { start, end: start + 30 * MINUTES_PER_DAY }\n })\n\n /** The furthest the view has gone either way, so the timeline keeps it. */\n const reach = ref<{ start: number; end: number } | null>(null)\n\n /** px of room kept before the view. */\n /** Whether there is a touch screen; read once mounted, so the server render matches. */\n const coarse = ref(false)\n const roomBefore = computed(() => {\n const width = o.viewWidth.value || 800\n if (!coarse.value) return ROOM_BEFORE_POINTER * width\n return Math.min(Math.max(ROOM_BEFORE * width, ROOM_BEFORE_MIN), ROOM_BEFORE_TIME * ppm.value)\n })\n\n const touch = useTouchScroll()\n\n /** Where the timeline would start, without a fixed range. */\n const freeStart = computed(() => {\n const start = Math.min(content.value.start, reach.value?.start ?? Infinity)\n return Math.max(EARLIEST, dayStart(start - roomBefore.value / ppm.value))\n })\n\n /**\n * The start held while a touch scroll goes on. A later start moves the\n * content, and the scroll position has to follow it, which iOS drops then:\n * content coming in earlier, the room changing as the screen turns. So the\n * start stays where it was and moves once the scroll is over.\n */\n const heldStart = ref<number | null>(null)\n watch(\n touch.active,\n (on) => {\n heldStart.value = on ? freeStart.value : null\n },\n { flush: 'sync' },\n )\n\n /** The span the timeline covers. */\n const extent = computed(() => {\n const fixed = o.range()?.map((text) => parseTime(text))\n if (fixed?.[0] && fixed[1])\n return { start: dayStart(fixed[0].at), end: dayStart(fixed[1].at) + MINUTES_PER_DAY }\n const width = o.viewWidth.value || 800\n const after = Math.max((ROOM_AFTER * width) / ppm.value, 7 * MINUTES_PER_DAY)\n const end = Math.max(content.value.end, reach.value?.end ?? -Infinity)\n return {\n start: heldStart.value ?? freeStart.value,\n end: dayStart(end + after) + MINUTES_PER_DAY,\n }\n })\n const origin = computed(() => extent.value.start)\n const totalWidth = computed(() => Math.ceil((extent.value.end - extent.value.start) * ppm.value))\n const xOf = (at: number) => (at - origin.value) * ppm.value\n\n // Scrolling ---------------------------------------------------------------------\n\n const scrollTop = ref(0)\n const scrollLeft = ref(0)\n /**\n * The time at the left edge of the view. It is what stays put: when the\n * extent grows or the zoom changes, the scroll position follows it.\n */\n const viewStart = ref(0)\n const scrollbar = ref(0)\n\n const link = createScrollLink('y')\n\n function onChartScroll() {\n const el = o.chart.value!\n const body = o.tableBody.value\n // iOS reports positions past either end while the content bounces back\n const { top, left } = scrollPositionOf(el)\n scrollTop.value = top\n scrollLeft.value = left\n viewStart.value = origin.value + left / ppm.value\n if (body) link.follow(el, body)\n grow()\n }\n\n /** Keeps time `at` on the timeline, adding room around it. */\n function keep(at: number, until = at) {\n const known = reach.value\n const start = Math.min(known?.start ?? at, at)\n const end = Math.max(known?.end ?? until, until)\n if (known?.start !== start || known.end !== end) reach.value = { start, end }\n }\n\n /**\n * Adds time at the end the view comes near. After the end that is safe at\n * any time. Before the start it moves the content right, and the scroll\n * position has to follow it; iOS drops that move while a touch scroll or\n * its momentum goes on, and the next scroll event would read the old\n * position against the new start. So the room before is wide enough for a\n * run of swipes, and is filled again once the scroll is over.\n */\n function grow() {\n const el = o.chart.value\n if (!el || o.range()) return\n const width = o.viewWidth.value\n const left = scrollPositionOf(el).left\n if (left + width > totalWidth.value - ROOM_AFTER * width)\n keep(viewStart.value, viewStart.value + ((ROOM_AFTER + 1) * width) / ppm.value)\n if (touch.active.value) touch.whenSettled(growBefore)\n // without a finger it is filled right away, but only near the start: a\n // scrollbar thumb dragged there would jump back under the pointer\n else if (left < 2 * width) growBefore()\n }\n\n function growBefore() {\n const el = o.chart.value\n // an end bouncing back reports positions past it\n if (!el || o.range() || el.scrollLeft < 0) return\n if (scrollPositionOf(el).left < roomBefore.value - o.viewWidth.value) keep(viewStart.value)\n }\n\n function onTableScroll() {\n const body = o.tableBody.value!\n const el = o.chart.value\n if (el) link.follow(body, el)\n }\n\n // keep the time at the left edge in place as the axis changes under it\n watch(\n [origin, ppm, totalWidth],\n () => {\n const el = o.chart.value\n if (!el) return\n const target = Math.round((viewStart.value - origin.value) * ppm.value)\n if (Math.abs(el.scrollLeft - target) >= 1) el.scrollLeft = target\n scrollLeft.value = scrollPositionOf(el).left\n },\n { flush: 'post' },\n )\n\n function measureScrollbar() {\n const el = o.chart.value\n if (el) scrollbar.value = el.offsetHeight - el.clientHeight\n }\n\n function scrollToTime(at: number, offset = 48) {\n // start the view on a whole header cell, so its label isn't cut at the edge\n const target = at - offset / ppm.value\n viewStart.value = unitStart(level.value.bottom, target, o.weekStart.value, headerStep.value)\n if (!o.range()) keep(viewStart.value, viewStart.value + o.viewWidth.value / ppm.value)\n const el = o.chart.value\n if (!el) return\n el.scrollLeft = Math.round((viewStart.value - origin.value) * ppm.value)\n scrollLeft.value = scrollPositionOf(el).left\n }\n\n function fit() {\n const width = o.fitWidth()\n if (!width) return\n const days = Math.max(1, (content.value.end - content.value.start) / MINUTES_PER_DAY)\n dayWidth.value = Math.min(FIT_MAX, Math.max(DAY_WIDTH_RANGE[0], (width - 96) / days))\n scrollToTime(content.value.start)\n }\n\n onMounted(() => {\n coarse.value = window.matchMedia?.('(any-pointer: coarse)').matches ?? false\n measureScrollbar()\n scrollToTime(content.value.start)\n })\n\n /** Fits the content once the timeline has a width, unless a `scale` was given. */\n function fitWhenSized() {\n if (!fitPending || !o.fitWidth() || !o.viewWidth.value) return\n fitPending = false\n fit()\n }\n\n // Zoom --------------------------------------------------------------------------\n\n /** Changes the day width, keeping the time under `anchor` (px from the view's left) in place. */\n function zoomTo(width: number, anchor = o.viewWidth.value / 2) {\n const next = Math.min(DAY_WIDTH_RANGE[1], Math.max(DAY_WIDTH_RANGE[0], width))\n const at = viewStart.value + anchor / ppm.value\n dayWidth.value = next\n viewStart.value = at - anchor / ppm.value\n // what was scrolled past at the old zoom could be very wide at the new one\n if (reach.value && !o.range()) {\n reach.value = null\n keep(viewStart.value, viewStart.value + o.viewWidth.value / ppm.value)\n }\n // a pinch moves the scroll position anyway, so the start moves with it\n if (heldStart.value != null) heldStart.value = freeStart.value\n }\n\n function onWheel(event: WheelEvent) {\n if (!(event.ctrlKey || event.metaKey)) return\n event.preventDefault()\n const rect = o.chart.value!.getBoundingClientRect()\n zoomTo(dayWidth.value * Math.exp(-event.deltaY * 0.01), event.clientX - rect.left)\n }\n\n const zoomIn = () => zoomTo(dayWidth.value * ZOOM_STEP)\n const zoomOut = () => zoomTo(dayWidth.value / ZOOM_STEP)\n\n /** Two fingers on the timeline zoom it, as a held Ctrl and the wheel do. */\n let pinch: { distance: number; dayWidth: number } | null = null\n\n /** Starts a pinch when two fingers rest on the timeline; `true` when one began. */\n function pinchStart(event: TouchEvent) {\n const el = o.chart.value\n if (event.touches.length !== 2 || !el) return false\n const { a, b, distance } = fingers(event)\n if (!el.contains(a.target as Node) || !el.contains(b.target as Node)) return false\n pinch = { distance: Math.max(1, distance), dayWidth: dayWidth.value }\n return true\n }\n\n /** Zooms with the fingers; `true` while a pinch is going on. */\n function pinchMove(event: TouchEvent) {\n if (!pinch || event.touches.length !== 2) return false\n event.preventDefault()\n const { a, b, distance } = fingers(event)\n const rect = o.chart.value!.getBoundingClientRect()\n zoomTo(pinch.dayWidth * (distance / pinch.distance), (a.clientX + b.clientX) / 2 - rect.left)\n return true\n }\n\n function pinchEnd(event: TouchEvent) {\n if (event.touches.length < 2) pinch = null\n }\n\n // What is in view ---------------------------------------------------------------\n\n /** The time in view, with some room either side. */\n const timeWindow = computed(() => ({\n start: origin.value + (scrollLeft.value - OVERSCAN_X) / ppm.value,\n end: origin.value + (scrollLeft.value + (o.viewWidth.value || 800) + OVERSCAN_X) / ppm.value,\n }))\n\n function cells(unit: TimeUnit, step = 1): HeaderCell[] {\n const { start, end } = timeWindow.value\n return unitsBetween(unit, Math.max(start, origin.value), end, o.weekStart.value, step).map(\n (cell) => ({\n key: cell.start,\n end: cell.end,\n left: xOf(cell.start),\n width: (cell.end - cell.start) * ppm.value,\n label: unitLabel(unit, cell.start, o.messages.value, dayWidth.value),\n }),\n )\n }\n\n const headerStep = computed(() => (level.value.bottom === 'hour' ? hourStep(dayWidth.value) : 1))\n const topCells = computed(() => cells(level.value.top))\n const bottomCells = computed(() => cells(level.value.bottom, headerStep.value))\n\n /** Days off, shaded; at hour level the hours outside work too. */\n const shaded = computed(() => {\n if (dayWidth.value < 8) return []\n const { start, end } = timeWindow.value\n return offTime(o.calendar.value, start, end, level.value.name === 'hour').map((range) => ({\n key: range.start,\n left: xOf(range.start),\n width: (range.end - range.start) * ppm.value,\n }))\n })\n\n const todayX = computed(() =>\n o.today() && now.value >= extent.value.start && now.value <= extent.value.end\n ? xOf(now.value)\n : null,\n )\n\n /** A pointer's x on screen as timeline px. */\n const contentX = (clientX: number) =>\n clientX - o.chart.value!.getBoundingClientRect().left + o.chart.value!.scrollLeft\n\n function scrollToDate(date: string) {\n const parsed = parseTime(date)\n if (parsed) scrollToTime(parsed.at)\n }\n\n function scrollToToday() {\n scrollToTime(now.value, o.viewWidth.value / 3)\n }\n\n return {\n dayWidth,\n level,\n ppm,\n now,\n extent,\n origin,\n totalWidth,\n xOf,\n contentX,\n scrollTop,\n scrollLeft,\n scrollbar,\n measureScrollbar,\n onChartScroll,\n onTableScroll,\n scrollToTime,\n scrollToDate,\n scrollToToday,\n fit,\n fitWhenSized,\n zoomTo,\n zoomIn,\n zoomOut,\n onWheel,\n pinchStart,\n pinchMove,\n pinchEnd,\n timeWindow,\n topCells,\n bottomCells,\n shaded,\n todayX,\n }\n}\n\nexport type TimeAxis = ReturnType<typeof useTimeAxis>\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { collaborationColors } from 'dou-ui';
2
+ import * as Y from 'yjs';
3
+ import { type GanttExtension } from '../core/extension';
4
+ import type { GanttDocument } from '../core/transaction';
5
+ import { type AwarenessLike, type GanttPresence } from './peers';
6
+ /** How the people editing together see each other. */
7
+ export interface CollaborationUser {
8
+ name: string;
9
+ /** The color of this user's outlines, any CSS color. Picked from the client id when left out. */
10
+ color?: string;
11
+ }
12
+ export interface CollaborationOptions {
13
+ /** Where the tasks live, e.g. `ydoc.getMap('tasks')`. */
14
+ tasks: Y.Map<unknown>;
15
+ /** Where the links live, e.g. `ydoc.getMap('links')`; in the same `Y.Doc` as `tasks`. */
16
+ links: Y.Map<unknown>;
17
+ /** Shares what each person selects and drags (`y-protocols/awareness`). */
18
+ awareness?: AwarenessLike;
19
+ /** This user, shown to the others. Needs `awareness`. */
20
+ user?: CollaborationUser;
21
+ }
22
+ export { collaborationColors };
23
+ /**
24
+ * Writes tasks and links into empty maps, e.g. when a plan that so far was
25
+ * saved as JSON starts being edited together. Every task gets an `order`
26
+ * key. Do it once, where the shared document is created.
27
+ */
28
+ export declare function fillMaps(maps: {
29
+ tasks: Y.Map<unknown>;
30
+ links: Y.Map<unknown>;
31
+ }, doc: GanttDocument): void;
32
+ /**
33
+ * Edit a Gantt chart together through a Yjs document. The tasks and links
34
+ * live in two `Y.Map`s: the chart's `v-model` only mirrors them, and
35
+ * changes made to `v-model` from outside are not written back. Undo only
36
+ * takes back this user's own changes. Folding a summary stays local.
37
+ */
38
+ export declare function collaboration(options: CollaborationOptions): GanttExtension;
39
+ export type { AwarenessLike, GanttPresence };
@@ -0,0 +1,22 @@
1
+ import { type Component } from 'vue';
2
+ /** The part of an `Awareness` (`y-protocols/awareness`) the chart uses. */
3
+ export interface AwarenessLike {
4
+ clientID: number;
5
+ getLocalState(): Record<string, unknown> | null;
6
+ setLocalStateField(field: string, value: unknown): void;
7
+ getStates(): Map<number, Record<string, unknown>>;
8
+ on(event: 'change', listener: (...args: unknown[]) => void): void;
9
+ off(event: 'change', listener: (...args: unknown[]) => void): void;
10
+ }
11
+ /** What each person shares under the awareness field `gantt`. */
12
+ export interface GanttPresence {
13
+ /** The tasks they are dragging, with the dates they would get. */
14
+ moving: {
15
+ id: string;
16
+ start?: string;
17
+ end?: string;
18
+ }[];
19
+ selection: string[];
20
+ }
21
+ /** A layer that outlines the tasks other people are dragging or have selected, in their colors. */
22
+ export declare function createPeers(awareness: AwarenessLike): Component;
@@ -0,0 +1,21 @@
1
+ type __VLS_Props = {
2
+ kind: 'text' | 'number' | 'date' | 'datetime';
3
+ value: string;
4
+ label: string;
5
+ placeholder?: string;
6
+ /** Keep editing after Enter, with the field emptied: for adding tasks one after another. */
7
+ repeat?: boolean;
8
+ /** Whether a value can be saved. */
9
+ check?: (value: string) => boolean;
10
+ };
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
+ done: (value: string | null) => any;
13
+ tab: (back: boolean) => any;
14
+ invalid: (value: string) => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onDone?: ((value: string | null) => any) | undefined;
17
+ onTab?: ((back: boolean) => any) | undefined;
18
+ onInvalid?: ((value: string) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import type { ContextMenuItem } from 'dou-ui';
2
+ type __VLS_Props = {
3
+ items: ContextMenuItem[];
4
+ };
5
+ type __VLS_ModelProps = {
6
+ 'open'?: boolean;
7
+ };
8
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
10
+ open: (x: number, y: number) => void | undefined;
11
+ close: () => void | undefined;
12
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:open": (value: boolean) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
15
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import type { HeaderCell } from '../time-axis';
2
+ type __VLS_Props = {
3
+ cells: HeaderCell[];
4
+ shaded: {
5
+ key: number;
6
+ left: number;
7
+ width: number;
8
+ }[];
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import type { WorkCalendar } from '../core/calendar';
2
+ import type { TimeUnit } from '../core/scale';
3
+ import type { HeaderCell } from '../time-axis';
4
+ type __VLS_Props = {
5
+ topCells: HeaderCell[];
6
+ bottomCells: HeaderCell[];
7
+ bottomUnit: TimeUnit;
8
+ calendar: WorkCalendar;
9
+ now: number;
10
+ todayX: number | null;
11
+ };
12
+ type __VLS_Slots = {
13
+ default?: () => unknown;
14
+ };
15
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
19
+ type __VLS_WithSlots<T, S> = T & {
20
+ new (): {
21
+ $slots: S;
22
+ };
23
+ };
@@ -0,0 +1,64 @@
1
+ import { type ComputedRef, type InjectionKey, type Ref } from 'vue';
2
+ import type { GanttLink, GanttTask } from './core/model';
3
+ import type { GanttCriticalPath } from './core/schedule';
4
+ import type { GanttScale } from './core/scale';
5
+ import type { GanttDocument } from './core/transaction';
6
+ import type { GanttRow } from './core/tree';
7
+ import type { GanttExpose } from './types';
8
+ import type { GanttMessages } from './locale';
9
+ /** The surrounding Gantt chart, as `useGantt()` and extensions see it. */
10
+ export interface GanttContext {
11
+ /** The whole document as it is now; change it with `transact`. */
12
+ doc: ComputedRef<GanttDocument>;
13
+ /** The document's tasks and links, as a shortcut. */
14
+ tasks: ComputedRef<GanttTask[]>;
15
+ links: ComputedRef<GanttLink[]>;
16
+ selection: Ref<string[]>;
17
+ /** The task the keyboard is on. */
18
+ active: Ref<string | null>;
19
+ readonly: ComputedRef<boolean>;
20
+ messages: ComputedRef<GanttMessages>;
21
+ scale: ComputedRef<GanttScale>;
22
+ /** How wide a day is, in px. */
23
+ dayWidth: Ref<number>;
24
+ /** Height of a row, in px. */
25
+ rowHeight: ComputedRef<number>;
26
+ /** The rows as laid out, with spans and summaries worked out. */
27
+ rows: ComputedRef<GanttRow[]>;
28
+ /**
29
+ * While a bar is dragged: the tasks it would change, with the ones it
30
+ * would push, as they would be written. `null` otherwise.
31
+ */
32
+ preview: ComputedRef<Map<string, Partial<GanttTask>> | null>;
33
+ transact: GanttExpose['transact'];
34
+ /** The critical path when `critical` is on, `null` otherwise. */
35
+ critical: ComputedRef<GanttCriticalPath | null>;
36
+ /** Links not kept by the dates as they are. */
37
+ brokenLinks: ComputedRef<Set<string>>;
38
+ schedule: GanttExpose['schedule'];
39
+ editCell: GanttExpose['editCell'];
40
+ insertTask: GanttExpose['insertTask'];
41
+ indentTask: GanttExpose['indentTask'];
42
+ outdentTask: GanttExpose['outdentTask'];
43
+ undo: () => boolean;
44
+ redo: () => boolean;
45
+ canUndo: Ref<boolean>;
46
+ canRedo: Ref<boolean>;
47
+ fit: () => void;
48
+ zoomIn: () => void;
49
+ zoomOut: () => void;
50
+ scrollToDate: GanttExpose['scrollToDate'];
51
+ scrollToTask: GanttExpose['scrollToTask'];
52
+ scrollToToday: () => void;
53
+ /** Puts the keyboard on a task, scrolls it into view and focuses its bar. */
54
+ focusTask: (id: string) => void;
55
+ /** Where a time (a date string or minutes) is on the timeline, in px from its start. */
56
+ xOf: (time: string | number) => number | null;
57
+ /** Where a task's row is on the timeline, in px from the top of the rows. */
58
+ rowTop: (id: string) => number | null;
59
+ /** Says `text` to screen readers. */
60
+ announce: (text: string) => void;
61
+ }
62
+ export declare const ganttKey: InjectionKey<GanttContext>;
63
+ /** The surrounding Gantt chart; for layers, overlays and the chart's slots. */
64
+ export declare function useGantt(): GanttContext;
@@ -0,0 +1,14 @@
1
+ import { type GanttTask, type TaskSpan } from './model';
2
+ /** A task's dates as planned, to compare the current ones with. */
3
+ export interface GanttBaselineItem {
4
+ id: string;
5
+ start: string;
6
+ end?: string;
7
+ }
8
+ /**
9
+ * Saves the current dates of every task as a baseline, summaries included
10
+ * (with the dates their children cover). Tasks without dates are left out.
11
+ */
12
+ export declare function takeBaseline(tasks: readonly GanttTask[]): GanttBaselineItem[];
13
+ /** The planned span of each task in a baseline, read as the task's own dates are. */
14
+ export declare function baselineSpans(baseline: readonly GanttBaselineItem[], milestones: (id: string) => boolean): Map<string, TaskSpan>;
@@ -0,0 +1,75 @@
1
+ /** Which days and hours are worked. */
2
+ export interface GanttCalendar {
3
+ /** Days of the week off, `0` is Sunday. Defaults to `[0, 6]`. */
4
+ weekends?: number[];
5
+ /** Days off besides the weekend, as `YYYY-MM-DD`. */
6
+ holidays?: string[];
7
+ /** Weekend days that are worked anyway, as `YYYY-MM-DD`. They win over `weekends`. */
8
+ workdays?: string[];
9
+ /**
10
+ * The working hours of a working day, as `HH:mm`: one stretch such as
11
+ * `['09:00', '18:00']` (the default), or several to leave out a break,
12
+ * such as `[['09:00', '12:00'], ['13:00', '18:00']]`.
13
+ */
14
+ hours?: [string, string] | [string, string][];
15
+ }
16
+ /** A calendar with its strings read, for fast lookups. */
17
+ export interface WorkCalendar {
18
+ isWorkday: (day: number) => boolean;
19
+ /** Working stretches of a day as minutes since midnight, in order and apart. */
20
+ hours: [number, number][];
21
+ }
22
+ export declare function resolveCalendar(calendar?: GanttCalendar): WorkCalendar;
23
+ /** Working days that start in `[from, to)`. */
24
+ export declare function workdaysBetween(calendar: WorkCalendar, from: number, to: number): number;
25
+ /** Working minutes in `[from, to)`: the working hours of working days. */
26
+ export declare function workingMinutes(calendar: WorkCalendar, from: number, to: number): number;
27
+ export interface TimeRange {
28
+ start: number;
29
+ end: number;
30
+ }
31
+ /**
32
+ * The stretches of `[from, to)` that are not worked: whole days off, and
33
+ * with `hours`, the hours outside work on working days too. Adjacent
34
+ * stretches are merged.
35
+ */
36
+ export declare function offTime(calendar: WorkCalendar, from: number, to: number, hours?: boolean): TimeRange[];
37
+ /**
38
+ * The midnight `count` working days away from the midnight `at`: forward
39
+ * it ends after the last of them, backward it is the start of the last
40
+ * one. A fraction rounds to whole days.
41
+ */
42
+ export declare function shiftWorkdays(calendar: WorkCalendar, at: number, count: number): number;
43
+ /**
44
+ * The moment `count` working minutes away from `at`, forward or backward.
45
+ * It lands inside working hours, or at the end of a stretch going forward.
46
+ */
47
+ export declare function shiftWorkMinutes(calendar: WorkCalendar, at: number, count: number): number;
48
+ /** Working days, or working minutes, in `[from, to)`. */
49
+ export declare const workIn: (calendar: WorkCalendar, from: number, to: number, dateOnly: boolean) => number;
50
+ /**
51
+ * Whether work can start at `at`: the start of a working day for whole
52
+ * days, a moment inside working hours otherwise.
53
+ */
54
+ export declare function isWorkTime(calendar: WorkCalendar, at: number, dateOnly: boolean): boolean;
55
+ /**
56
+ * The first moment at or after `at` when work can start: the next working
57
+ * midnight for whole days, the next working minute otherwise.
58
+ */
59
+ export declare function nextWorkTime(calendar: WorkCalendar, at: number, dateOnly: boolean): number;
60
+ /**
61
+ * The end of a span moved to start at `start`, keeping its working time:
62
+ * as many working days for a date-only span, as many working minutes
63
+ * otherwise. A span with no working time keeps its length.
64
+ */
65
+ export declare function endKeepingWork(calendar: WorkCalendar, span: {
66
+ start: number;
67
+ end: number;
68
+ dateOnly: boolean;
69
+ }, start: number): number;
70
+ /** The start of a span moved to end at `end`, keeping its working time. */
71
+ export declare function startKeepingWork(calendar: WorkCalendar, span: {
72
+ start: number;
73
+ end: number;
74
+ dateOnly: boolean;
75
+ }, end: number): number;
@@ -0,0 +1,77 @@
1
+ import type { GanttColumn } from '../types';
2
+ import { type GanttMessages } from '../locale';
3
+ import { type WorkCalendar } from './calendar';
4
+ import { type GanttTask } from './model';
5
+ import type { GanttTransaction } from './transaction';
6
+ /** A value `readCSV` couldn't read; the task keeps what it had, or goes without it. */
7
+ export interface GanttCSVWarning {
8
+ /** The row of the file it is on, the header being row 1. */
9
+ row: number;
10
+ /** The column's `key`; `id` and `parent` for the columns `ids` adds. */
11
+ column: string;
12
+ /** The text in the cell. */
13
+ text: string;
14
+ }
15
+ /** What `readCSV` and `importCSV` give back. */
16
+ export interface GanttCSVResult {
17
+ /** In the order of the file. */
18
+ tasks: GanttTask[];
19
+ /** Values that couldn't be read. */
20
+ warnings: GanttCSVWarning[];
21
+ /**
22
+ * The keys of the columns the file has, in its order; `id` and `parent`
23
+ * for the columns `ids` adds.
24
+ */
25
+ columns: string[];
26
+ /** Headers that are no field and no column of the chart; their values are left out. */
27
+ unknownColumns: string[];
28
+ }
29
+ /** Why a file can't be read as tasks, as the `code` of a `GanttCSVError`. */
30
+ export type GanttCSVErrorCode = 'noTitleColumn';
31
+ /** Thrown when a file can't be read as tasks: for developers in `message`, for people in `reason`. */
32
+ export declare class GanttCSVError extends Error {
33
+ readonly code: GanttCSVErrorCode;
34
+ readonly reason: string;
35
+ name: string;
36
+ constructor(message: string, code: GanttCSVErrorCode, reason: string);
37
+ }
38
+ /** How `importCSV` puts the tasks of a file into the chart. */
39
+ export interface GanttCSVImportOptions {
40
+ /**
41
+ * `replace` (the default): the file's tasks become the chart's. A task
42
+ * whose id is already in the chart is updated with what the file has
43
+ * and keeps the rest, links too; the tasks the file doesn't have are
44
+ * removed. It is the default as the usual way back is the file the
45
+ * chart wrote, edited in a spreadsheet. `append`: the file's tasks are
46
+ * added after the chart's, with new ids where theirs are taken.
47
+ */
48
+ mode?: 'replace' | 'append';
49
+ }
50
+ /**
51
+ * Splits CSV into rows of fields (RFC 4180); a byte order mark at the start
52
+ * is dropped. `delimiter` defaults to the one the text most likely uses.
53
+ */
54
+ export declare function splitCSV(text: string, delimiter?: string): string[][];
55
+ /**
56
+ * A date as tasks write it, from the ways spreadsheets save one:
57
+ * `2026/3/2`, `2026-3-2`, `2026.3.2`, with `9:30` or `09:30:00` after it.
58
+ * `null` when it isn't a date.
59
+ */
60
+ export declare function readCSVDate(text: string): string | null;
61
+ export interface ReadCSVContext {
62
+ columns: GanttColumn[];
63
+ messages: GanttMessages;
64
+ calendar: WorkCalendar;
65
+ /** Tasks outside the file that a task of it may be under, as when appending. */
66
+ outer?: (id: string) => boolean;
67
+ }
68
+ /**
69
+ * Reads the tasks of a CSV file. Throws when no header is the title
70
+ * column, as the file then isn't a list of tasks.
71
+ */
72
+ export declare function readGanttCSV(text: string, context: ReadCSVContext): GanttCSVResult;
73
+ /**
74
+ * Puts the tasks `readGanttCSV` read into a transaction, as
75
+ * `GanttCSVImportOptions.mode` says.
76
+ */
77
+ export declare function applyGanttCSV(tr: GanttTransaction, result: GanttCSVResult, mode: 'replace' | 'append', calendar: WorkCalendar): void;
@@ -0,0 +1,51 @@
1
+ import { type GanttExtension } from './extension';
2
+ import type { GanttDocument } from './transaction';
3
+ export interface DocumentProblem {
4
+ code: 'version' | 'shape' | 'missing-id' | 'duplicate-id' | 'missing-parent' | 'parent-cycle' | 'bad-date' | 'missing-task' | 'bad-lag' | 'self-link' | 'nested-link' | 'duplicate-link' | 'link-cycle' | 'refused-link';
5
+ /** The task at fault, or the task a link points at. */
6
+ taskId?: string;
7
+ /** The link at fault. */
8
+ linkId?: string;
9
+ /**
10
+ * Where the problem is, as a path into the document, e.g. `tasks[2].start`
11
+ * or `links[0].to`; `version` for the version.
12
+ */
13
+ path: string;
14
+ /** The id, date or lag at fault, for the editor's message. */
15
+ text: string;
16
+ /** An English description, for logs. */
17
+ message: string;
18
+ }
19
+ export interface CheckDocumentOptions {
20
+ /** The chart's `canLink`, to check the links against it too. */
21
+ canLink?: GanttRule;
22
+ /** The chart's extensions, to check the links against their `canLink` too. */
23
+ extensions?: readonly (GanttExtension | readonly GanttExtension[])[];
24
+ }
25
+ type GanttRule = NonNullable<GanttExtension['canLink']>;
26
+ /**
27
+ * The problems of a Gantt document, before it is saved or shown: an
28
+ * unknown version, tasks and links without an id or sharing one, `parent`
29
+ * pointing nowhere or in a circle, dates and lags that can't be read, and
30
+ * links to missing tasks, to the task itself, between a summary and its
31
+ * own task, twice between the same tasks, or in a circle; with `canLink`
32
+ * or `extensions`, links their rules refuse too.
33
+ */
34
+ export declare function checkDocument(document: GanttDocument, options?: CheckDocumentOptions): DocumentProblem[];
35
+ /**
36
+ * Brings a saved document up to the current version, and fills what an
37
+ * older or hand-written one may lack (`version`, `tasks`, `links`). Tasks
38
+ * and links that are not objects are dropped; anything else is kept as it
39
+ * is. Throws for data that is not an object
40
+ * (an array, `null`, a string) and for a version it does not know.
41
+ */
42
+ export declare function migrateDocument(input: unknown): GanttDocument;
43
+ /**
44
+ * The `v-model` as the chart reads it. A document of the wrong shape (not an
45
+ * object, without `tasks` or `links`, without a known `version`) gets a
46
+ * warning in development and is read with what is missing left empty and the
47
+ * version as 1, so the chart still opens and what it writes back carries
48
+ * `version: 1`. The scheduler doesn't use `links` and doesn't need them.
49
+ */
50
+ export declare function readDocument(value: unknown, needsLinks?: boolean): GanttDocument;
51
+ export {};
@@ -0,0 +1,88 @@
1
+ import type { ContextMenuItem } from 'dou-ui';
2
+ import type { Component } from 'vue';
3
+ import type { GanttContext } from '../context';
4
+ import type { GanttColumn, GanttMarker, GanttMove } from '../types';
5
+ import type { GanttMenuTarget } from './menu';
6
+ import type { GanttLink } from './model';
7
+ import type { GanttDocument, GanttTransaction } from './transaction';
8
+ /** What an extension's hooks receive; the same as `useGantt()`. */
9
+ export type GanttExtensionContext = GanttContext;
10
+ /**
11
+ * A key handler: return `false` to let the key through to the next handler
12
+ * and the chart's own keys; anything else means it was handled.
13
+ */
14
+ export type GanttKeyHandler = (event: KeyboardEvent) => boolean | void;
15
+ /** An undo history that replaces the chart's own, e.g. one that only undoes this user's changes. */
16
+ export interface GanttHistoryHandler {
17
+ /** `false` when there is nothing to undo. */
18
+ undo: () => boolean;
19
+ redo: () => boolean;
20
+ canUndo: () => boolean;
21
+ canRedo: () => boolean;
22
+ }
23
+ /**
24
+ * A piece of the Gantt chart: table columns, rules for links and moves,
25
+ * keys, marker lines and extra layers. A third-party extension can do
26
+ * anything the chart's own props can.
27
+ */
28
+ export interface GanttExtension {
29
+ name: string;
30
+ /** Higher runs first: its keys and rules are asked before others. Defaults to 100. */
31
+ priority?: number;
32
+ /** Columns added after the table's own. */
33
+ columns?: GanttColumn[];
34
+ /** A link rule, asked with `can-link`. Return `true` to allow, or a sentence saying why not. */
35
+ canLink?: (link: Pick<GanttLink, 'from' | 'to' | 'type'>, doc: GanttDocument) => boolean | string;
36
+ /** A rule for new dates, asked with `can-move`. */
37
+ canMove?: (move: GanttMove, doc: GanttDocument) => boolean | string;
38
+ /** Keys, written as in `useHotkey` (e.g. `mod+d`, `shift+t`), while the chart has focus. */
39
+ keymap?: (context: GanttExtensionContext) => Record<string, GanttKeyHandler>;
40
+ /**
41
+ * Items for the context menu, which opens on a right click, the menu key
42
+ * or a long press. They come before the built-in ones.
43
+ */
44
+ contextMenu?: (context: GanttExtensionContext, target: GanttMenuTarget) => ContextMenuItem[];
45
+ /** Vertical marker lines, such as a release day; worked out again when what they read changes. */
46
+ markers?: (context: GanttExtensionContext) => GanttMarker[];
47
+ /**
48
+ * Components drawn on the timeline, in its own coordinates: `below` under
49
+ * the dependency lines, `above` over the bars. They scroll with it.
50
+ */
51
+ layers?: {
52
+ below?: Component[];
53
+ above?: Component[];
54
+ };
55
+ /** Components drawn over the chart, not scrolled, e.g. a panel. They can call `useGantt()`. */
56
+ overlays?: Component[];
57
+ /** Runs once the chart is mounted. Return a function to run when it unmounts. */
58
+ setup?: (context: GanttExtensionContext) => void | (() => void);
59
+ /**
60
+ * Sees every change before it reaches the tasks and links, in priority
61
+ * order, and may add steps to it. Undo and redo come through here too.
62
+ */
63
+ transaction?: (tr: GanttTransaction, context: GanttExtensionContext) => void;
64
+ /** Replaces the chart's undo history; the first extension that has one wins. */
65
+ history?: (context: GanttExtensionContext) => GanttHistoryHandler;
66
+ }
67
+ /** Declares an extension; only gives it a type. */
68
+ export declare function defineGanttExtension(extension: GanttExtension): GanttExtension;
69
+ /** Everything the extensions contribute, gathered once per list. */
70
+ export interface ResolvedGanttExtensions {
71
+ extensions: GanttExtension[];
72
+ columns: GanttColumn[];
73
+ linkRules: NonNullable<GanttExtension['canLink']>[];
74
+ moveRules: NonNullable<GanttExtension['canMove']>[];
75
+ keymaps: NonNullable<GanttExtension['keymap']>[];
76
+ markers: NonNullable<GanttExtension['markers']>[];
77
+ menus: NonNullable<GanttExtension['contextMenu']>[];
78
+ setups: NonNullable<GanttExtension['setup']>[];
79
+ transactions: NonNullable<GanttExtension['transaction']>[];
80
+ history: GanttExtension['history'];
81
+ below: Component[];
82
+ above: Component[];
83
+ overlays: Component[];
84
+ }
85
+ type ExtensionList = readonly (GanttExtension | readonly GanttExtension[])[];
86
+ /** Sorts the extensions by priority and collects what they add. */
87
+ export declare function resolveGanttExtensions(list?: ExtensionList): ResolvedGanttExtensions;
88
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { GanttLinkType } from './model';
2
+ export interface Point {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /**
7
+ * A path through `points` with straight segments and corners rounded by
8
+ * up to `radius`; a corner never takes more than half of either segment.
9
+ */
10
+ export declare function roundedPath(points: readonly Point[], radius?: number): string;
11
+ /** Where a bar sits on screen, in px. */
12
+ export interface BarBox {
13
+ /** Left and right edges; equal for a milestone's centre. */
14
+ left: number;
15
+ right: number;
16
+ /** Vertical centre. */
17
+ y: number;
18
+ }
19
+ export interface LinkRoute {
20
+ points: Point[];
21
+ /** The arrow's tip, and which way it points: `1` right, `-1` left. */
22
+ tip: Point;
23
+ direction: 1 | -1;
24
+ }
25
+ /**
26
+ * The route of a dependency line: out of one end of `from`, into one end
27
+ * of `to`. It leaves and arrives horizontally, `gap` px clear of the bars,
28
+ * and takes a single vertical run when one fits; otherwise it turns back
29
+ * along the boundary between the rows (`rowHeight` apart).
30
+ */
31
+ export declare function routeLink(from: BarBox, to: BarBox, type?: GanttLinkType, rowHeight?: number, gap?: number): LinkRoute;
32
+ /** A small solid arrowhead with its tip at `tip`. */
33
+ export declare function arrowPath(tip: Point, direction: 1 | -1, size?: number): string;
@@ -0,0 +1,19 @@
1
+ import { type GanttDocument, type GanttTransaction } from './transaction';
2
+ export declare class GanttHistory {
3
+ private depth;
4
+ private done;
5
+ private undone;
6
+ constructor(depth?: number);
7
+ get canUndo(): boolean;
8
+ get canRedo(): boolean;
9
+ record(tr: GanttTransaction, selectionBefore: string[], selectionAfter: string[], now?: number): void;
10
+ undo(doc: GanttDocument): {
11
+ tr: GanttTransaction;
12
+ selection: string[];
13
+ } | null;
14
+ redo(doc: GanttDocument): {
15
+ tr: GanttTransaction;
16
+ selection: string[];
17
+ } | null;
18
+ clear(): void;
19
+ }
@@ -0,0 +1,20 @@
1
+ import type { GanttLink, GanttLinkType, GanttTask } from './model';
2
+ /** Why a new link was refused. */
3
+ export type GanttLinkRefusal = 'self' | 'nested' | 'duplicate' | 'cycle' | 'custom';
4
+ /** Whether `to` can be reached from `from` along the links. */
5
+ export declare function reaches(links: readonly GanttLink[], from: string, to: string): boolean;
6
+ /**
7
+ * Why a link from `from` to `to` can't be made, or `null` when it can: a
8
+ * task can't depend on itself, on its own parent or child, on a task it
9
+ * already depends on, or on a task that waits for it.
10
+ */
11
+ export declare function linkRefusal(tasks: ReadonlyMap<string, GanttTask>, links: readonly GanttLink[], from: string, to: string): GanttLinkRefusal | null;
12
+ /** The link type for a line drawn from one end of a task to one end of another. */
13
+ export declare function linkTypeOf(fromEnd: 'start' | 'end', toEnd: 'start' | 'end'): GanttLinkType;
14
+ /** A link's lag, read: an amount of working days, or of working minutes. */
15
+ export interface GanttLag {
16
+ amount: number;
17
+ unit: 'day' | 'minute';
18
+ }
19
+ /** Reads a `lag`: a number of working days, or `'2d'`, `'4h'`, `'30m'`; `null` when unreadable. */
20
+ export declare function parseLag(lag: number | string | undefined): GanttLag | null;