UNPKG

ts-gantt

Version:

simple gantt chart written using typescript

1,078 lines (1,039 loc) 120 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.tsGantt = {})); })(this, (function (exports) { 'use strict'; const styles = ` <style> :host { --tsg-table-min-width-final: var(--tsg-table-min-width, 100px); --tsg-chart-min-width-final: var(--tsg-chart-min-width, 100px); --tsg-nesting-indent-final: var(--tsg-nesting-indent, 20px); --tsg-background-color-final: var(--tsg-background-color, white); --tsg-foreground-color-final: var(--tsg-foreground-color, black); --tsg-separator-color-final: var(--tsg-separator-color, dimgray); --tsg-header-color-final: var(--tsg-header-color, ghostwhite); --tsg-border-color-final: var(--tsg-border-color, lightgray); --tsg-symbol-color-final: var(--tsg-symbol-color, dimgray); --tsg-selection-color-final: var(--tsg-selection-color, ghostwhite); --tsg-scrollbar-track-color-final: var(--tsg-scrollbar-track-color, #eeeeee); --tsg-scrollbar-thumb-color-final: var(--tsg-scrollbar-thumb-color, #b0b0b0); --tsg-not-started-fg-color-final: var(--tsg-not-started-fg-color, dimgray); --tsg-in-progress-fg-color-final: var(--tsg-in-progress-fg-color, black); --tsg-overdue-fg-color-final: var(--tsg-overdue-fg-color, darkred); --tsg-completed-fg-color-final: var(--tsg-completed-fg-color, darkgreen); --tsg-completed-late-fg-color-final: var(--tsg-completed-late-fg-color, sienna); --tsg-today-line-color-final: var(--tsg-today-line-color, orangered); --tsg-chart-bar-color-1-final: var(--tsg-chart-bar-color-1, skyblue); --tsg-chart-bar-color-2-final: var(--tsg-chart-bar-color-2, lightcoral); --tsg-chart-bar-accent-1-final: var(--tsg-chart-bar-accent-1, darkcyan); --tsg-chart-bar-accent-2-final: var(--tsg-chart-bar-accent-2, darkred); --tsg-font-family-final: var(--tsg-font-family, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif); --tsg-font-size-final: var(--tsg-font-size, 14px); --tsg-line-height-final: var(--tsg-line-height, 16px); --tsg-max-cell-text-lines-final: var(--tsg-max-cell-text-lines, 2); } .tsg-no-text-selection { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .tsg-wrapper { position: relative; box-sizing: border-box; display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-start; width: 100%; height: 100%; overflow-y: auto; overflow-x: hidden; background-color: var(--tsg-background-color-final); } .tsg-wrapper * { box-sizing: border-box; } .tsg-separator { display: flex; flex: 0 0 auto; width: var(--tsg-separator-width); height: 100%; padding: 0; cursor: ew-resize; background-color: var(--tsg-separator-color-final); } .tsg-table-wrapper { position: relative; display: flex; flex-direction: column; width: calc(50% - 5px); height: 100%; min-width: var(--tsg-table-min-width-final); overflow-y: auto; overflow-x: scroll; } .tsg-table { width: 100%; border-spacing: 0; font-family: var(--tsg-font-family-final); color: var(--tsg-foreground-color-final); table-layout: fixed; } .tsg-table-header { position: sticky; top: 0; padding: 5px; height: var(--tsg-header-height) !important; font-size: var(--tsg-font-size-final); border-width: var(--tsg-gridlines-width) var(--tsg-gridlines-width) var(--tsg-gridlines-width) 0; border-color: var(--tsg-border-color-final); border-style: solid; background-color: var(--tsg-header-color-final); z-index: 5; } .tsg-table-col-resizer { position: absolute; top: 0; right: 0; width: 10px; height: 100%; cursor: col-resize; } .tsg-table-body-row { height: var(--tsg-row-height) !important; } .tsg-table-body-row.selected { background-color: var(--tsg-selection-color-final); } .selected .tsg-table-body-cell { font-weight: bold; } .not-started .tsg-table-body-cell { color: var(--tsg-not-started-fg-color-final); } .in-progress .tsg-table-body-cell { color: var(--tsg-in-progress-fg-color-final); } .overdue .tsg-table-body-cell { color: var(--tsg-overdue-fg-color-final); } .completed .tsg-table-body-cell { color: var(--tsg-completed-fg-color-final); } .completed-late .tsg-table-body-cell { color: var(--tsg-completed-late-fg-color-final); } .tsg-table-body-cell { padding: 0; cursor: default; border-width: 0 var(--tsg-gridlines-width) var(--tsg-gridlines-width) 0; border-color: var(--tsg-border-color-final); border-style: solid; } .tsg-table-body-cell-text-wrapper { display: flex; align-items: center; height: calc(var(--tsg-row-height) - 2px); padding: 2px; } .tsg-table-body-cell-text-wrapper.start { justify-content: flex-start; } .tsg-table-body-cell-text-wrapper.center { justify-content: center; } .tsg-table-body-cell-text-wrapper.end { justify-content: flex-end; } .tsg-table-body-cell-text-expander { width: var(--tsg-nesting-indent-final) !important; min-width: var(--tsg-nesting-indent-final) !important; color: var(--tsg-symbol-color-final); text-align: center; cursor: pointer; } .tsg-table-body-cell-text-expander.nesting-1 { padding-left: var(--tsg-nesting-indent-final) !important; width: calc(var(--tsg-nesting-indent-final) * 2) !important; min-width: calc(var(--tsg-nesting-indent-final) * 2) !important; } .tsg-table-body-cell-text-expander.nesting-2 { padding-left: calc(var(--tsg-nesting-indent-final) * 2) !important; width: calc(var(--tsg-nesting-indent-final) * 3) !important; min-width: calc(var(--tsg-nesting-indent-final) * 3) !important; } .tsg-table-body-cell-text-expander.nesting-3 { padding-left: calc(var(--tsg-nesting-indent-final) * 3) !important; width: calc(var(--tsg-nesting-indent-final) * 4) !important; min-width: calc(var(--tsg-nesting-indent-final) * 4) !important; } .tsg-table-body-cell-text-expander.nesting-4 { padding-left: calc(var(--tsg-nesting-indent-final) * 4) !important; width: calc(var(--tsg-nesting-indent-final) * 5) !important; min-width: calc(var(--tsg-nesting-indent-final) * 5) !important; } .tsg-table-body-cell-text-expander.nesting-5 { padding-left: calc(var(--tsg-nesting-indent-final) * 5) !important; width: calc(var(--tsg-nesting-indent-final) * 6) !important; min-width: calc(var(--tsg-nesting-indent-final) * 6) !important; } .tsg-table-body-cell-text-expander.nesting-6 { padding-left: calc(var(--tsg-nesting-indent-final) * 6) !important; width: calc(var(--tsg-nesting-indent-final) * 7) !important; min-width: calc(var(--tsg-nesting-indent-final) * 7) !important; } .tsg-table-body-cell-text-expander.nesting-7 { padding-left: calc(var(--tsg-nesting-indent-final) * 7) !important; width: calc(var(--tsg-nesting-indent-final) * 8) !important; min-width: calc(var(--tsg-nesting-indent-final) * 8) !important; } .tsg-table-body-cell-text-expander.nesting-8 { padding-left: calc(var(--tsg-nesting-indent-final) * 8) !important; width: calc(var(--tsg-nesting-indent-final) * 9) !important; min-width: calc(var(--tsg-nesting-indent-final) * 9) !important; } .tsg-table-body-cell-text-expander.nesting-9 { padding-left: calc(var(--tsg-nesting-indent-final) * 9) !important; width: calc(var(--tsg-nesting-indent-final) * 10) !important; min-width: calc(var(--tsg-nesting-indent-final) * 10) !important; } .tsg-table-body-cell-text-expander.nesting-max { padding-left: calc(var(--tsg-nesting-indent-final) * 10) !important; width: calc(var(--tsg-nesting-indent-final) * 11) !important; min-width: calc(var(--tsg-nesting-indent-final) * 11) !important; } .tsg-table-body-cell-text { position: relative; overflow: hidden; max-height: calc(var(--tsg-max-cell-text-lines-final) * var(--tsg-line-height-final)); font-size: var(--tsg-font-size-final); line-height: var(--tsg-line-height-final); display: -webkit-box; -webkit-line-clamp: var(--tsg-max-cell-text-lines-final); -webkit-box-orient: vertical; } .tsg-chart-wrapper { position: relative; display: flex; flex-direction: column; width: 50%; height: 100%; min-width: var(--tsg-chart-min-width-final); overflow-y: auto; overflow-x: scroll; } .tsg-chart { display: flex; flex-direction: column; flex-shrink: 0; } .tsg-chart-header { position: sticky; top: 0; } .tsg-chart-header-bg { fill: var(--tsg-header-color-final); stroke-width: var(--tsg-gridlines-width); stroke: var(--tsg-border-color-final); } .tsg-chart-header-text { font-family: var(--tsg-font-family-final); font-size: var(--tsg-font-size-final); font-weight: bold; } .tsg-chart-header-gl, .tsg-chart-body-gl { stroke-width: var(--tsg-gridlines-width); stroke: var(--tsg-border-color-final); shape-rendering: crispEdges; } .tsg-chart-body-gl-today { stroke-width: calc(var(--tsg-gridlines-width) + 2px); stroke: var(--tsg-today-line-color-final); } .tsg-chart-body-bg { fill: transparent; } .tsg-chart-row-bg { fill: var(--tsg-background-color-final); } .tsg-chart-row-bg.selected { fill: var(--tsg-selection-color-final); } .tsg-chart-row { fill: transparent; } .tsg-chart-bar-planned { fill: var(--tsg-chart-bar-color-1-final); stroke: var(--tsg-chart-bar-color-1-final); stroke-width: var(--tsg-bar-stroke-width); } .selected .tsg-chart-bar-planned { stroke: var(--tsg-chart-bar-accent-1-final); } .tsg-chart-bar-planned-progress { fill: var(--tsg-chart-bar-accent-1-final); stroke: var(--tsg-chart-bar-accent-1-final); stroke-width: var(--tsg-bar-stroke-width); } .tsg-chart-bar-actual { fill: var(--tsg-chart-bar-color-2-final); stroke: var(--tsg-chart-bar-color-2-final); stroke-width: var(--tsg-bar-stroke-width); } .selected .tsg-chart-bar-actual { stroke: var(--tsg-chart-bar-accent-2-final); } .tsg-chart-bar-actual-progress { fill: var(--tsg-chart-bar-accent-2-final); stroke: var(--tsg-chart-bar-accent-2-final); stroke-width: var(--tsg-bar-stroke-width); } .tsg-chart-bar-handle { fill: var(--tsg-symbol-color-final); cursor: ew-resize; } .tsg-table-wrapper::-webkit-scrollbar { width: 0; height: 15px; } .tsg-chart-wrapper::-webkit-scrollbar { width: 15px; height: 15px; } .tsg-table-wrapper::-webkit-scrollbar-track, .tsg-chart-wrapper::-webkit-scrollbar-track { border-radius: 0; background: var(--tsg-scrollbar-track-color-final); } .tsg-table-wrapper::-webkit-scrollbar-thumb, .tsg-chart-wrapper::-webkit-scrollbar-thumb { border-radius: 0; background: var(--tsg-scrollbar-thumb-color-final); } </style> `; const TsGanttConst = { CSS_VAR_SEPARATOR_WIDTH: "--tsg-separator-width", CSS_VAR_HEADER_HEIGHT: "--tsg-header-height", CSS_VAR_ROW_HEIGHT: "--tsg-row-height", CSS_VAR_GRIDLINES_WIDTH: "--tsg-gridlines-width", CSS_VAR_BAR_STROKE_WIDTH: "--tsg-bar-stroke-width", EVENTS: { ROW_CLICK: "tsgrowclick", ROW_CONTEXT_MENU: "tsgrowcontextmenu", TABLE_COLUMN_REORDER: "tsgtablecolreorder", TABLE_BODY_CELL_EXPANDER_CLICK: "tsgexpanderclick", HANDLE_MOVE: "tsghandlemove", HANDLE_MOVE_END: "tsghandlemoveend", TASK_CHANGED_IN_CHART: "tsgtaskchangedinchart", }, CLASSES: { ROOT: { TEXT_SELECTION_DISABLED: "tsg-no-text-selection", MAIN_ELEMENT: "tsg-wrapper", CHART_WRAPPER: "tsg-chart-wrapper", TABLE_WRAPPER: "tsg-table-wrapper", FOOTER: "tsg-footer", SEPARATOR: "tsg-separator", ROW_SELECTED: "selected", }, CHART: { MAIN_ELEMENT: "tsg-chart", HEADER: "tsg-chart-header", HEADER_BACKGROUND: "tsg-chart-header-bg", HEADER_GRIDLINES: "tsg-chart-header-gl", HEADER_TEXT: "tsg-chart-header-text", BODY: "tsg-chart-body", BODY_BACKGROUND: "tsg-chart-body-bg", BODY_GRIDLINES: "tsg-chart-body-gl", BODY_TODAY_LINE: "tsg-chart-body-gl-today", ROW_WRAPPER: "tsg-chart-row-wrapper", ROW: "tsg-chart-row", ROW_BACKGROUND: "tsg-chart-row-bg", BAR: { GROUP: "tsg-chart-bar-group", WRAPPER: "tsg-chart-bar-wrapper", PLANNED: "tsg-chart-bar-planned", PLANNED_PROGRESS: "tsg-chart-bar-planned-progress", ACTUAL: "tsg-chart-bar-actual", ACTUAL_PROGRESS: "tsg-chart-bar-actual-progress", HANDLE_WRAPPER: "tsg-chart-bar-handle-wrapper", HANDLE: "tsg-chart-bar-handle", }, }, TABLE: { MAIN_ELEMENT: "tsg-table", COLUMN_RESIZER: "tsg-table-col-resizer", HEADER: "tsg-table-header", BODY_ROW: "tsg-table-body-row", BODY_CELL: "tsg-table-body-cell", BODY_CELL_TEXT_WRAPPER: "tsg-table-body-cell-text-wrapper", BODY_CELL_TEXT: "tsg-table-body-cell-text", BODY_CELL_EXPANDER: "tsg-table-body-cell-text-expander", BODY_CELL_EXPANDER_NESTING_PREFIX: "nesting-", }, } }; class TsGanttOptions { constructor(item = null) { this.bindParentDatesToChild = false; this.enableChartEdit = false; this.multilineSelection = true; this.useCtrlKeyForMultilineSelection = true; this.drawTodayLine = true; this.highlightRowsDependingOnTaskState = true; this.columnsMinWidthPx = [200, 100, 100, 100, 100, 100, 100, 100]; this.columnsContentAlign = ["start", "end", "center", "center", "center", "center", "center", "center"]; this.separatorWidthPx = 5; this.headerHeightPx = 90; this.rowHeightPx = 40; this.borderWidthPx = 1; this.barStrokeWidthPx = 2; this.barMarginPx = 2; this.barCornerRadiusPx = 6; this.rowSymbols = { childless: "•", collapsed: "▾", expanded: "▴", }; this.chartShowProgress = true; this.chartDisplayMode = "both"; this.chartScale = "month"; this.chartDateOffsetDays = { "day": 14, "week": 60, "month": 240, "year": 730, }; this.chartDateOffsetDaysMin = { "day": 7, "week": 30, "month": 120, "year": 365, }; this.chartDayWidthPx = { "day": 60, "week": 20, "month": 3, "year": 1, }; this.locale = "en"; this.localeDecimalSeparator = { en: ".", uk: ",", ru: ",", ja: ".", }; this.localeDateFormat = { en: "MM/DD/YYYY", uk: "DD.MM.YYYY", ru: "DD.MM.YYYY", ja: "YYYY/MM/DD", }; this.localeFirstWeekDay = { en: 0, uk: 1, ru: 1, ja: 0, }; this.localeDateMonths = { en: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], uk: ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"], ru: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"], ja: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"], }; this.localeDateDays = { en: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], uk: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота"], ru: ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"], ja: ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"], }; this.localeDateDaysShort = { en: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], uk: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"], ru: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"], ja: ["日", "月", "火", "水", "木", "金", "土"], }; this.localeDateScale = { en: ["Weeks", "Months", "Years"], uk: ["Тижні", "Місяці", "Роки"], ru: ["Недели", "Месяцы", "Годы"], ja: ["週間", "月間", "年間"], }; this.localeFooters = { en: ["Total tasks", "Completed"], uk: ["Всього задач", "Завершено"], ru: ["Всего задач", "Завершено"], ja: ["総タスク", "完了"], }; this.localeHeaders = { en: ["Name", "Progress", "Start date planned", "End date planned", "Start date actual", "End date actual", "Duration planned", "Duration actual"], uk: ["Ім'я", "Прогрес", "Дата початку планована", "Дата завершення планована", "Дата початку фактична", "Дата завершення фактична", "Тривалість планована", "Тривалість фактична"], ru: ["Имя", "Прогресс", "Дата начала планируемая", "Дата окончания планируемая", "Дата начала фактическая", "Дата окончания фактическая", "Длительность планируемая", "Длительность фактическая"], ja: ["タイトル", "進捗", "予定開始日", "予定終了日", "実績開始日", "実績終了日", "予定日数", "実績日数"], }; this.localeDurationFormatters = { en: (duration) => duration === 1 ? "1 day" : duration + " days", uk: (duration) => { let d = duration % 100; if (d > 10 && d < 20) { return duration + " днів"; } else { d = d % 10; if (d === 1) { return duration + " день"; } else if (d < 5 && d > 0) { return duration + " дні"; } else { return duration + " днів"; } } }, ru: (duration) => { let d = duration % 100; if (d > 10 && d < 20) { return duration + " дней"; } else { d = d % 10; if (d === 1) { return duration + " день"; } else if (d < 5 && d > 0) { return duration + " дня"; } else { return duration + " дней"; } } }, ja: (duration) => duration === 1 ? "1 日" : duration + " 日間", }; this.columnValueGetters = [ (task) => task.localizedNames && task.localizedNames[this.locale] || task.name, (task) => (+task.progress.toFixed(2)).toLocaleString("en-US") .replace(".", this.localeDecimalSeparator[this.locale] || ".") + " %", (task) => task.datePlannedStart ? task.datePlannedStart.format(this.localeDateFormat[this.locale] || "L") : "", (task) => task.datePlannedEnd ? task.datePlannedEnd.format(this.localeDateFormat[this.locale] || "L") : "", (task) => task.dateActualStart ? task.dateActualStart.format(this.localeDateFormat[this.locale] || "L") : "", (task) => task.dateActualEnd ? task.dateActualEnd.format(this.localeDateFormat[this.locale] || "L") : "", (task) => { if (!task.datePlannedEnd || !task.datePlannedStart) { return ""; } const end = task.datePlannedEnd; const start = task.datePlannedStart; const duration = end.diff(start, "day") + 1; return this.localeDurationFormatters[this.locale] ? this.localeDurationFormatters[this.locale](duration) : duration.toString(); }, (task) => { if (!task.dateActualEnd || !task.dateActualStart) { return ""; } const end = task.dateActualEnd; const start = task.dateActualStart; const duration = end.diff(start, "day") + 1; return this.localeDurationFormatters[this.locale] ? this.localeDurationFormatters[this.locale](duration) : duration.toString(); }, ]; if (item != null) { Object.assign(this, item); } } get dayWidthPx() { return this.chartDayWidthPx[this.chartScale]; } } var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; var dayjs_min = {exports: {}}; (function (module, exports) { !function(t,e){module.exports=e();}(commonjsGlobal,(function(){var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",f="month",h="quarter",c="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return "["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return !r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return (e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return -t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,f),s=n-i<0,u=e.clone().add(r+(s?-1:1),f);return +(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return {M:f,y:c,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:h}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p=function(t){return t instanceof _},S=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else {var a=e.name;D[a]=e,i=a;}return !r&&i&&(g=i),i||!r&&g},w=function(t,e){if(p(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},O=v;O.l=S,O.i=p,O.w=function(t,e){return w(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=S(t.locale,null,!0),this.parse(t);}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(O.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init();},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds();},m.$utils=function(){return O},m.isValid=function(){return !(this.$d.toString()===l)},m.isSame=function(t,e){var n=w(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return w(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<w(t)},m.$g=function(t,e,n){return O.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!O.u(e)||e,h=O.p(t),l=function(t,e){var i=O.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return O.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(h){case c:return r?l(1,0):l(31,11);case f:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=O.p(t),h="set"+(this.$u?"UTC":""),l=(n={},n[a]=h+"Date",n[d]=h+"Date",n[f]=h+"Month",n[c]=h+"FullYear",n[u]=h+"Hours",n[s]=h+"Minutes",n[i]=h+"Seconds",n[r]=h+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===f||o===c){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d;}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[O.p(t)]()},m.add=function(r,h){var d,l=this;r=Number(r);var $=O.p(h),y=function(t){var e=w(l);return O.w(e.date(e.date()+Math.round(t*r)),l)};if($===f)return this.set(f,this.$M+r);if($===c)return this.set(c,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return O.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=O.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,f=n.months,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},c=function(t){return O.s(s%12||12,t,"0")},d=n.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},$={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:O.s(a+1,2,"0"),MMM:h(n.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:O.s(this.$D,2,"0"),d:String(this.$W),dd:h(n.weekdaysMin,this.$W,o,2),ddd:h(n.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:O.s(s,2,"0"),h:c(1),hh:c(2),a:d(s,u,!0),A:d(s,u,!1),m:String(u),mm:O.s(u,2,"0"),s:String(this.$s),ss:O.s(this.$s,2,"0"),SSS:O.s(this.$ms,3,"0"),Z:i};return r.replace(y,(function(t,e){return e||$[t]||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=O.p(d),M=w(r),m=(M.utcOffset()-this.utcOffset())*e,v=this-M,g=O.m(this,M);return g=($={},$[c]=g/12,$[f]=g,$[h]=g/3,$[o]=(v-m)/6048e5,$[a]=(v-m)/864e5,$[u]=v/n,$[s]=v/e,$[i]=v/t,$)[y]||v,l?g:O.a(g)},m.daysInMonth=function(){return this.endOf(f).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=S(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return O.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),T=_.prototype;return w.prototype=T,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",f],["$y",c],["$D",d]].forEach((function(t){T[t[1]]=function(e){return this.$g(e,t[0],t[1])};})),w.extend=function(t,e){return t.$i||(t(e,_,w),t.$i=!0),w},w.locale=S,w.isDayjs=p,w.unix=function(t){return w(1e3*t)},w.en=D[g],w.Ls=D,w.p={},w})); } (dayjs_min)); var dayjs = dayjs_min.exports; function getRandomUuid() { return crypto.getRandomValues(new Uint32Array(4)).join("-"); } function compareTwoStringSets(setA, setB) { if (setA.size !== setB.size) { return false; } const commonSet = new Set([...setA, ...setB]); return setA.size === commonSet.size; } function createSvgElement(elementTag, classList = [], attributes = [], parent = null, innerHtml = null) { const element = document.createElementNS("http://www.w3.org/2000/svg", elementTag); for (const attribute of attributes) { element.setAttribute(attribute[0], attribute[1]); } if (classList.length !== 0) { element.classList.add(...classList); } if (innerHtml) { element.innerHTML = innerHtml; } if (parent) { parent.append(element); } return element; } function getAllDatesBetweenTwoDates(start, end) { const dateStart = start.startOf("day"); const dateEnd = end.startOf("day"); if (!dateStart || !dateEnd || dateEnd.diff(dateStart) < 0) { return []; } if (dateEnd.diff(dateStart) === 0) { return [dateStart]; } const dates = []; let currentDate = dateStart; while (currentDate.isBefore(dateEnd) || currentDate.isSame(dateEnd)) { dates.push(currentDate); currentDate = currentDate.add(1, "day"); } return dates; } class TsGanttTask { constructor(source, id, parentId, name, localizedNames, progress, datePlannedStart, datePlannedEnd, dateActualStart, dateActualEnd, nestingLvl, hasChildren, parentUuid, uuid, shown, expanded) { Object.assign(this, source !== null && source !== void 0 ? source : {}); if (arguments.length < 2) { return; } this.externalId = id; this.parentExternalId = parentId; this.uuid = uuid || getRandomUuid(); this.parentUuid = parentUuid; this.nestingLvl = nestingLvl !== null && nestingLvl !== void 0 ? nestingLvl : 0; this.hasChildren = hasChildren !== null && hasChildren !== void 0 ? hasChildren : false; this.name = name; this.localizedNames = localizedNames; this.datePlannedStart = datePlannedStart ? dayjs(datePlannedStart) : null; this.datePlannedEnd = datePlannedEnd ? dayjs(datePlannedEnd) : null; this.dateActualStart = dateActualStart ? dayjs(dateActualStart) : null; this.dateActualEnd = dateActualEnd ? dayjs(dateActualEnd) : null; this.progress = (!progress || progress < 0) ? 0 : progress > 100 ? 100 : progress; this.shown = shown !== null && shown !== void 0 ? shown : false; this.expanded = expanded !== null && expanded !== void 0 ? expanded : false; } static convertModelsToTasks(taskModels, idMap = new Map()) { const models = taskModels.slice(); const allParentIds = new Set(models.map(x => x.parentId)); const tasks = []; let currentLevelTasks = []; for (let i = models.length - 1; i >= 0; i--) { const model = models[i]; if (!model.parentId) { const newTask = new TsGanttTask(model, model.id, model.parentId, model.name, model.localizedNames, model.progress, model.datePlannedStart, model.datePlannedEnd, model.dateActualStart, model.dateActualEnd, 0, allParentIds.has(model.id), null, idMap.get(model.id)); tasks.push(newTask); currentLevelTasks.push(newTask); models.splice(i, 1); } } let currentNestingLvl = 1; while (currentLevelTasks.length !== 0) { const nextLevelTasks = []; currentLevelTasks.filter(x => x.hasChildren).forEach(task => { for (let i = models.length - 1; i >= 0; i--) { const model = models[i]; if (model.parentId === task.externalId) { const newTask = new TsGanttTask(model, model.id, model.parentId, model.name, model.localizedNames, model.progress, model.datePlannedStart, model.datePlannedEnd, model.dateActualStart, model.dateActualEnd, currentNestingLvl, allParentIds.has(model.id), task.uuid, idMap.get(model.id)); tasks.push(newTask); nextLevelTasks.push(newTask); models.splice(i, 1); } } }); currentLevelTasks = nextLevelTasks; currentNestingLvl++; } return tasks; } static detectTaskChanges(data) { const { oldTasks, newTasks } = data; const oldTaskByUuid = new Map(); oldTasks.forEach(task => { oldTaskByUuid.set(task.uuid, task); }); const newUuids = new Set(); newTasks.forEach(task => { newUuids.add(task.uuid); }); const deleted = oldTasks.filter(x => !newUuids.has(x.uuid)); const added = []; const changed = []; const all = []; for (const newTask of newTasks) { if (!oldTaskByUuid.has(newTask.uuid)) { added.push(newTask); all.push(newTask); continue; } const oldTask = oldTaskByUuid.get(newTask.uuid); if (!newTask.equals(oldTask)) { changed.push(newTask); all.push(newTask); } else { all.push(oldTask); } } return { deleted, added, changed, all }; } static createTasksIdMap(tasks) { const idsMap = new Map(); for (const task of tasks) { if (!idsMap.has(task.externalId)) { idsMap.set(task.externalId, task.uuid); } } return idsMap; } static checkPaternity(tasks, parent, child) { var _a; let parentUuid = child.parentUuid; while (parentUuid) { if (parentUuid === parent.uuid) { return true; } parentUuid = (_a = tasks.find(x => x.uuid === parentUuid)) === null || _a === void 0 ? void 0 : _a.parentUuid; } return false; } static checkForCollapsedParent(tasks, task) { while (task.parentUuid) { task = tasks.find(x => x.uuid === task.parentUuid); if (!task.expanded) { return true; } } return false; } static sortTasksRecursively(tasks, parentUuid) { const tasksFiltered = tasks.filter(x => x.parentUuid === parentUuid) .sort(TsGanttTask.defaultComparer); const sorted = []; for (const task of tasksFiltered) { sorted.push(task); sorted.push(...this.sortTasksRecursively(tasks, task.uuid)); } return sorted; } static getMinMaxDates(tasks) { let minDate = dayjs(); let maxDate = dayjs(); for (const task of tasks) { const plannedStart = dayjs(task.datePlannedStart); const plannedEnd = dayjs(task.datePlannedEnd); const actualStart = task.dateActualStart ? dayjs(task.dateActualStart) : null; const actualEnd = task.dateActualEnd ? dayjs(task.dateActualEnd) : null; if (plannedStart.isBefore(minDate)) { minDate = plannedStart; } if (plannedEnd.isAfter(maxDate)) { maxDate = plannedEnd; } if (actualStart && actualStart.isBefore(minDate)) { minDate = actualStart; } if (actualEnd && actualEnd.isAfter(maxDate)) { maxDate = actualEnd; } } return { minDate, maxDate }; } equals(another) { var _a, _b, _c, _d, _e, _f, _g, _h; return this.externalId === another.externalId && this.parentExternalId === another.parentExternalId && this.nestingLvl === another.nestingLvl && this.hasChildren === another.hasChildren && this.name === another.name && this.progress === another.progress && ((_a = this.datePlannedStart) === null || _a === void 0 ? void 0 : _a.unix()) === ((_b = another.datePlannedStart) === null || _b === void 0 ? void 0 : _b.unix()) && ((_c = this.datePlannedEnd) === null || _c === void 0 ? void 0 : _c.unix()) === ((_d = another.datePlannedEnd) === null || _d === void 0 ? void 0 : _d.unix()) && ((_e = this.dateActualStart) === null || _e === void 0 ? void 0 : _e.unix()) === ((_f = another.dateActualStart) === null || _f === void 0 ? void 0 : _f.unix()) && ((_g = this.dateActualEnd) === null || _g === void 0 ? void 0 : _g.unix()) === ((_h = another.dateActualEnd) === null || _h === void 0 ? void 0 : _h.unix()); } compareTo(another) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; if (this.nestingLvl > another.nestingLvl) { return 1; } if (this.nestingLvl < another.nestingLvl) { return -1; } if ((((_a = this.datePlannedStart) === null || _a === void 0 ? void 0 : _a.unix()) || 0) > (((_b = another.datePlannedStart) === null || _b === void 0 ? void 0 : _b.unix()) || 0)) { return 1; } if ((((_c = this.datePlannedStart) === null || _c === void 0 ? void 0 : _c.unix()) || 0) < ((_d = another.datePlannedStart) === null || _d === void 0 ? void 0 : _d.unix()) || 0) { return -1; } if ((((_e = this.datePlannedEnd) === null || _e === void 0 ? void 0 : _e.unix()) || 0) > (((_f = another.datePlannedEnd) === null || _f === void 0 ? void 0 : _f.unix()) || 0)) { return 1; } if ((((_g = this.datePlannedEnd) === null || _g === void 0 ? void 0 : _g.unix()) || 0) < (((_h = another.datePlannedEnd) === null || _h === void 0 ? void 0 : _h.unix()) || 0)) { return -1; } if ((((_j = this.dateActualStart) === null || _j === void 0 ? void 0 : _j.unix()) || 0) > (((_k = another.dateActualStart) === null || _k === void 0 ? void 0 : _k.unix()) || 0)) { return 1; } if ((((_l = this.dateActualStart) === null || _l === void 0 ? void 0 : _l.unix()) || 0) < (((_m = another.dateActualStart) === null || _m === void 0 ? void 0 : _m.unix()) || 0)) { return -1; } if ((((_o = this.dateActualEnd) === null || _o === void 0 ? void 0 : _o.unix()) || 0) > (((_p = another.dateActualEnd) === null || _p === void 0 ? void 0 : _p.unix()) || 0)) { return 1; } if ((((_q = this.dateActualEnd) === null || _q === void 0 ? void 0 : _q.unix()) || 0) < (((_r = another.dateActualEnd) === null || _r === void 0 ? void 0 : _r.unix()) || 0)) { return -1; } return this.name.localeCompare(another.name); } getState() { if (this.progress === 0) { return "not-started"; } if (this.progress === 100) { if (this.datePlannedEnd) { if ((this.dateActualEnd && this.dateActualEnd.isAfter(this.datePlannedEnd)) || (this.dateActualStart && this.dateActualStart.isAfter(this.datePlannedEnd))) { return "completed-late"; } } return "completed"; } if (this.datePlannedEnd && this.datePlannedEnd.isBefore(dayjs().startOf("day"))) { return "overdue"; } return "in-progress"; } toModel() { var _a, _b, _c, _d; const model = {}; Object.assign(model, this); model.id = this.externalId; model.parentId = this.parentExternalId; model.name = this.name; model.progress = this.progress; model.datePlannedStart = ((_a = this.datePlannedStart) === null || _a === void 0 ? void 0 : _a.toDate()) || null; model.datePlannedEnd = ((_b = this.datePlannedEnd) === null || _b === void 0 ? void 0 : _b.toDate()) || null; model.dateActualStart = ((_c = this.dateActualStart) === null || _c === void 0 ? void 0 : _c.toDate()) || null; model.dateActualEnd = ((_d = this.dateActualEnd) === null || _d === void 0 ? void 0 : _d.toDate()) || null; model.localizedNames = this.localizedNames; return model; } toggleExpanded() { this.expanded = !this.expanded; } getMinMaxDates(chartBarMode) { const { datePlannedStart, datePlannedEnd, dateActualStart, dateActualEnd } = this; const plannedDatesSet = datePlannedStart && datePlannedEnd; const actualDatesSet = dateActualStart && dateActualEnd; let minDate; let maxDate; if (chartBarMode === "both") { if (actualDatesSet || plannedDatesSet) { if (actualDatesSet && plannedDatesSet) { minDate = datePlannedStart.isBefore(dateActualStart) ? datePlannedStart : dateActualStart; maxDate = datePlannedEnd.isAfter(dateActualEnd) ? datePlannedEnd : dateActualEnd; } else if (plannedDatesSet) { minDate = datePlannedStart; maxDate = datePlannedEnd; } else { minDate = dateActualStart; maxDate = dateActualEnd; } } } else if (chartBarMode === "planned" && plannedDatesSet) { minDate = datePlannedStart; maxDate = datePlannedEnd; } else if (chartBarMode === "actual" && actualDatesSet) { minDate = dateActualStart; maxDate = dateActualEnd; } return { minDate, maxDate }; } getHorizontalOffsetPx(chartBarMode, chartMinDate, dayWidthPx) { const { minDate: taskMinDate } = this.getMinMaxDates(chartBarMode); if (!taskMinDate) { return null; } const offsetX = taskMinDate.diff(chartMinDate, "day") * dayWidthPx; return offsetX; } updateParents() { } applyChangesFrom(source) { this.datePlannedStart = source.datePlannedStart; this.datePlannedEnd = source.datePlannedEnd; this.dateActualStart = source.dateActualStart; this.dateActualEnd = source.dateActualEnd; this.progress = source.progress; } clone() { return new TsGanttTask(this); } } TsGanttTask.defaultComparer = (a, b) => a.compareTo(b); class TsGanttData { constructor(options) { this._tasks = []; this._taskByUuid = new Map(); this._tasksByParentUuid = new Map(); this._selectedTasks = []; this._options = options; } get options() { return this._options; } get dateMinOffset() { return this._dateMinOffset; } get dateMaxOffset() { return this._dateMaxOffset; } get tasks() { return this._tasks; } get models() { return this._tasks.map(x => x.toModel()); } get selectedTasks() { return this._selectedTasks; } get selectedModels() { return this._selectedTasks.map(x => x.toModel()); } getTaskByUuid(uuid) { return this._taskByUuid.get(uuid); } getAllTasksAsChanged() { const minMaxDatesUpdated = this.updateMinMaxDates(); return { deleted: [], added: [], changed: this._tasks, all: this._tasks, datesChanged: minMaxDatesUpdated, }; } getShownTaskUuidsRecursively(parentUuid = null) { const tasks = this._tasksByParentUuid.get(parentUuid) || []; const uuids = []; for (const task of tasks) { uuids.push(task.uuid); if (task.expanded) { uuids.push(...this.getShownTaskUuidsRecursively(task.uuid)); } } return uuids; } updateTasks(taskModels) { const oldTasks = this._tasks; const oldTasksIdMap = TsGanttTask.createTasksIdMap(oldTasks); const newTasks = TsGanttTask.convertModelsToTasks(taskModels, oldTasksIdMap); const changes = TsGanttTask.detectTaskChanges({ oldTasks, newTasks }); this.updateInternalTaskCollections(changes.all); this.groupAndSortTasks(); const datesChanged = this.updateMinMaxDates(); return Object.assign({}, changes, { datesChanged }); } updateSingleTask(updatedTask) { const currentTask = this._taskByUuid.get(updatedTask.uuid); if (!currentTask) { return { deleted: [], added: [], changed: [], all: this._tasks, datesChanged: false, }; } currentTask.applyChangesFrom(updatedTask); if (this.options.bindParentDatesToChild) ; return { deleted: [], added: [], changed: [currentTask], all: this._tasks, datesChanged: this.updateMinMaxDates(), }; } expandAllTasks(state) { for (const task of this._tasks) { task.expanded = state; if (task.parentUuid) { task.shown = state; } } } updateSelectedTasks(tasks) { let newSelectedTasks; if ((tasks === null || tasks === void 0 ? void 0 : tasks.length) && !(tasks[0] instanceof TsGanttTask)) { const ids = new Set(tasks.map(x => x.id)); newSelectedTasks = this._tasks.filter(x => ids.has(x.externalId)); } else { newSelectedTasks = tasks || []; } const oldSelectedTasks = this._selectedTasks; const selectionEmpty = oldSelectedTasks.length === 0 && newSelectedTasks.length === 0; if (selectionEmpty) { return null; } const oldUuids = oldSelectedTasks.map(x => x.uuid); const newUuids = newSelectedTasks.map(x => x.uuid); const selectionNotChanged = compareTwoStringSets(new Set(oldUuids), new Set(newUuids)); if (selectionNotChanged) { return { selected: newUuids, selectedTasks: newSelectedTasks, deselected: [], deselectedTasks: [], changed: false, }; } this._selectedTasks = newSelectedTasks; const deselectedTasks = oldSelectedTasks.filter(x => !newUuids.includes(x.uuid)); const deselectedUuids = deselectedTasks.map(x => x.uuid); return { selected: newUuids, selectedTasks: newSelectedTasks, deselected: deselectedUuids, deselectedTasks, changed: true, }; } refreshSelectedTasks() { const tasks = this._selectedTasks.filter(x => !TsGanttTask.checkForCollapsedParent(this._tas