tsichart-core 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1254 -5
- package/dist/index.js +1006 -1416
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1006 -1416
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1040 -1446
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/index.css +9151 -7048
- package/dist/styles/index.css.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -132,7 +132,7 @@ const swimlaneLabelConstants = {
|
|
|
132
132
|
swimLaneLabelHeightPadding: 8,
|
|
133
133
|
labelLeftPadding: 28
|
|
134
134
|
};
|
|
135
|
-
const CharactersToEscapeForExactSearchInstance = ['"', '`', '\'', '!', '(', ')', '^', '[', '{', ':', '
|
|
135
|
+
const CharactersToEscapeForExactSearchInstance = ['"', '`', '\'', '!', '(', ')', '^', '[', '{', ':', '}', ']', '~', '/', '\\', '@', '#', '$', '%', '&', '*', ';', '=', '.', '_', '-', '<', '>', ',', '?'];
|
|
136
136
|
const NONNUMERICTOPMARGIN = 8;
|
|
137
137
|
const LINECHARTTOPPADDING = 16;
|
|
138
138
|
const GRIDCONTAINERCLASS = 'tsi-gridContainer';
|
|
@@ -1568,35 +1568,41 @@ class Component {
|
|
|
1568
1568
|
}
|
|
1569
1569
|
}
|
|
1570
1570
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1571
|
+
/**
|
|
1572
|
+
* Constants for Legend component layout and behavior
|
|
1573
|
+
*/
|
|
1574
|
+
const LEGEND_CONSTANTS = {
|
|
1575
|
+
/** Height in pixels for each numeric split-by item (includes type selector dropdown) */
|
|
1576
|
+
NUMERIC_SPLITBY_HEIGHT: 44,
|
|
1577
|
+
/** Height in pixels for each non-numeric (categorical/events) split-by item */
|
|
1578
|
+
NON_NUMERIC_SPLITBY_HEIGHT: 24,
|
|
1579
|
+
/** Height in pixels for the series name label header */
|
|
1580
|
+
NAME_LABEL_HEIGHT: 24,
|
|
1581
|
+
/** Buffer distance in pixels from scroll edge before triggering "load more" */
|
|
1582
|
+
SCROLL_BUFFER: 40,
|
|
1583
|
+
/** Number of split-by items to load per batch when paginating */
|
|
1584
|
+
BATCH_SIZE: 20,
|
|
1585
|
+
/** Minimum height in pixels for aggregate container */
|
|
1586
|
+
MIN_AGGREGATE_HEIGHT: 201,
|
|
1587
|
+
/** Minimum width in pixels for each series label in compact mode */
|
|
1588
|
+
MIN_SERIES_WIDTH: 124,
|
|
1589
|
+
};
|
|
1573
1590
|
class Legend extends Component {
|
|
1574
1591
|
constructor(drawChart, renderTarget, legendWidth) {
|
|
1575
1592
|
super(renderTarget);
|
|
1576
1593
|
this.renderSplitBys = (aggKey, aggSelection, dataType, noSplitBys) => {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
var firstSplitByType = firstSplitBy ? firstSplitBy.visibleType : null;
|
|
1580
|
-
Object.keys(this.chartComponentData.displayState[aggKey].splitBys).reduce((isSame, curr) => {
|
|
1581
|
-
return (firstSplitByType == this.chartComponentData.displayState[aggKey].splitBys[curr].visibleType) && isSame;
|
|
1582
|
-
}, true);
|
|
1583
|
-
let showMoreSplitBys = () => {
|
|
1584
|
-
const oldShownSplitBys = this.chartComponentData.displayState[aggKey].shownSplitBys;
|
|
1585
|
-
this.chartComponentData.displayState[aggKey].shownSplitBys = Math.min(oldShownSplitBys + 20, splitByLabelData.length);
|
|
1586
|
-
if (oldShownSplitBys != this.chartComponentData.displayState[aggKey].shownSplitBys) {
|
|
1587
|
-
this.renderSplitBys(aggKey, aggSelection, dataType, noSplitBys);
|
|
1588
|
-
}
|
|
1589
|
-
};
|
|
1594
|
+
const splitByLabelData = Object.keys(this.chartComponentData.timeArrays[aggKey]);
|
|
1595
|
+
const showMoreSplitBys = () => this.handleShowMoreSplitBys(aggKey, splitByLabelData, aggSelection, dataType, noSplitBys);
|
|
1590
1596
|
let splitByContainer = aggSelection.selectAll(".tsi-splitByContainer").data([aggKey]);
|
|
1591
|
-
|
|
1597
|
+
const splitByContainerEntered = splitByContainer.enter().append("div")
|
|
1592
1598
|
.merge(splitByContainer)
|
|
1593
1599
|
.classed("tsi-splitByContainer", true);
|
|
1594
|
-
|
|
1600
|
+
const splitByLabels = splitByContainerEntered.selectAll('.tsi-splitByLabel')
|
|
1595
1601
|
.data(splitByLabelData.slice(0, this.chartComponentData.displayState[aggKey].shownSplitBys), function (d) {
|
|
1596
1602
|
return d;
|
|
1597
1603
|
});
|
|
1598
|
-
|
|
1599
|
-
|
|
1604
|
+
const self = this;
|
|
1605
|
+
const splitByLabelsEntered = splitByLabels
|
|
1600
1606
|
.enter()
|
|
1601
1607
|
.append("div")
|
|
1602
1608
|
.merge(splitByLabels)
|
|
@@ -1610,135 +1616,60 @@ class Legend extends Component {
|
|
|
1610
1616
|
}
|
|
1611
1617
|
})
|
|
1612
1618
|
.on("click", function (event, splitBy) {
|
|
1613
|
-
|
|
1614
|
-
self.toggleSplitByVisible(aggKey, splitBy);
|
|
1615
|
-
}
|
|
1616
|
-
else {
|
|
1617
|
-
self.toggleSticky(aggKey, splitBy);
|
|
1618
|
-
}
|
|
1619
|
-
self.drawChart();
|
|
1619
|
+
self.handleSplitByClick(aggKey, splitBy);
|
|
1620
1620
|
})
|
|
1621
1621
|
.on("mouseover", function (event, splitBy) {
|
|
1622
1622
|
event.stopPropagation();
|
|
1623
|
-
self.
|
|
1623
|
+
self.handleSplitByMouseOver(aggKey, splitBy);
|
|
1624
1624
|
})
|
|
1625
1625
|
.on("mouseout", function (event) {
|
|
1626
1626
|
event.stopPropagation();
|
|
1627
|
-
self.
|
|
1628
|
-
.attr("stroke-opacity", 1)
|
|
1629
|
-
.attr("fill-opacity", 1);
|
|
1630
|
-
self.labelMouseout(self.svgSelection, aggKey);
|
|
1627
|
+
self.handleSplitByMouseOut(aggKey);
|
|
1631
1628
|
})
|
|
1632
1629
|
.attr("class", (splitBy, i) => {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
return `tsi-splitByLabel
|
|
1630
|
+
const compact = (dataType !== DataTypes.Numeric) ? 'tsi-splitByLabelCompact' : '';
|
|
1631
|
+
const shown = Utils.getAgVisible(self.chartComponentData.displayState, aggKey, splitBy) ? 'shown' : '';
|
|
1632
|
+
return `tsi-splitByLabel ${compact} ${shown}`;
|
|
1636
1633
|
})
|
|
1637
|
-
.classed("stickied", (splitBy, i) =>
|
|
1638
|
-
|
|
1639
|
-
return aggKey == self.chartComponentData.stickiedKey.aggregateKey && splitBy == self.chartComponentData.stickiedKey.splitBy;
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1642
|
-
var colors = Utils.createSplitByColors(self.chartComponentData.displayState, aggKey, self.chartOptions.keepSplitByColor);
|
|
1634
|
+
.classed("stickied", (splitBy, i) => self.isStickied(aggKey, splitBy));
|
|
1635
|
+
// Use helper methods to render each split-by element
|
|
1643
1636
|
splitByLabelsEntered.each(function (splitBy, j) {
|
|
1644
|
-
|
|
1637
|
+
const selection = d3__namespace.select(this);
|
|
1638
|
+
// Add color key (conditionally based on data type and legend state)
|
|
1645
1639
|
if (dataType === DataTypes.Numeric || noSplitBys || self.legendState === 'compact') {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
.append("div")
|
|
1649
|
-
.attr("class", 'tsi-colorKey')
|
|
1650
|
-
.merge(colorKey);
|
|
1651
|
-
if (dataType === DataTypes.Numeric) {
|
|
1652
|
-
colorKeyEntered.style('background-color', (d) => {
|
|
1653
|
-
return d;
|
|
1654
|
-
});
|
|
1655
|
-
}
|
|
1656
|
-
else {
|
|
1657
|
-
self.createNonNumericColorKey(dataType, colorKeyEntered, aggKey);
|
|
1658
|
-
}
|
|
1659
|
-
d3__namespace.select(this).classed('tsi-nonCompactNonNumeric', (dataType === DataTypes.Categorical || dataType === DataTypes.Events) && this.legendState !== 'compact');
|
|
1660
|
-
colorKey.exit().remove();
|
|
1640
|
+
self.addColorKey(selection, aggKey, splitBy, dataType);
|
|
1641
|
+
selection.classed('tsi-nonCompactNonNumeric', (dataType === DataTypes.Categorical || dataType === DataTypes.Events) && self.legendState !== 'compact');
|
|
1661
1642
|
}
|
|
1662
1643
|
else {
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
if (d3__namespace.select(this).select('.tsi-eyeIcon').empty()) {
|
|
1666
|
-
d3__namespace.select(this).append("button")
|
|
1667
|
-
.attr("class", "tsi-eyeIcon")
|
|
1668
|
-
.attr('aria-label', () => {
|
|
1669
|
-
let showOrHide = self.chartComponentData.displayState[aggKey].splitBys[splitBy].visible ? self.getString('hide series') : self.getString('show series');
|
|
1670
|
-
return `${showOrHide} ${splitBy} ${self.getString('in group')} ${self.chartComponentData.displayState[aggKey].name}`;
|
|
1671
|
-
})
|
|
1672
|
-
.attr('title', () => self.getString('Show/Hide values'))
|
|
1673
|
-
.on("click", function (event) {
|
|
1674
|
-
event.stopPropagation();
|
|
1675
|
-
self.toggleSplitByVisible(aggKey, splitBy);
|
|
1676
|
-
d3__namespace.select(this)
|
|
1677
|
-
.classed("shown", Utils.getAgVisible(self.chartComponentData.displayState, aggKey, splitBy));
|
|
1678
|
-
self.drawChart();
|
|
1679
|
-
});
|
|
1680
|
-
}
|
|
1681
|
-
if (d3__namespace.select(this).select('.tsi-seriesName').empty()) {
|
|
1682
|
-
let seriesName = d3__namespace.select(this)
|
|
1683
|
-
.append('div')
|
|
1684
|
-
.attr('class', 'tsi-seriesName');
|
|
1685
|
-
Utils.appendFormattedElementsFromString(seriesName, noSplitBys ? (self.chartComponentData.displayState[aggKey].name) : splitBy);
|
|
1644
|
+
selection.selectAll('.tsi-colorKey').remove();
|
|
1686
1645
|
}
|
|
1646
|
+
// Add eye icon
|
|
1647
|
+
self.addEyeIcon(selection, aggKey, splitBy);
|
|
1648
|
+
// Add series name
|
|
1649
|
+
self.addSeriesName(selection, aggKey, splitBy);
|
|
1650
|
+
// Add series type selection for numeric data
|
|
1687
1651
|
if (dataType === DataTypes.Numeric) {
|
|
1688
|
-
|
|
1689
|
-
d3__namespace.select(this).append("select")
|
|
1690
|
-
.attr('aria-label', `${self.getString("Series type selection for")} ${splitBy} ${self.getString('in group')} ${self.chartComponentData.displayState[aggKey].name}`)
|
|
1691
|
-
.attr('class', 'tsi-seriesTypeSelection')
|
|
1692
|
-
.on("change", function (data) {
|
|
1693
|
-
var seriesType = d3__namespace.select(this).property("value");
|
|
1694
|
-
self.chartComponentData.displayState[aggKey].splitBys[splitBy].visibleType = seriesType;
|
|
1695
|
-
self.drawChart();
|
|
1696
|
-
})
|
|
1697
|
-
.on("click", (event) => {
|
|
1698
|
-
event.stopPropagation();
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
d3__namespace.select(this).select('.tsi-seriesTypeSelection')
|
|
1702
|
-
.each(function (d) {
|
|
1703
|
-
var typeLabels = d3__namespace.select(this).selectAll('option')
|
|
1704
|
-
.data(data => self.chartComponentData.displayState[aggKey].splitBys[splitBy].types.map((type) => {
|
|
1705
|
-
return {
|
|
1706
|
-
type: type,
|
|
1707
|
-
aggKey: aggKey,
|
|
1708
|
-
splitBy: splitBy,
|
|
1709
|
-
visibleMeasure: Utils.getAgVisibleMeasure(self.chartComponentData.displayState, aggKey, splitBy)
|
|
1710
|
-
};
|
|
1711
|
-
}));
|
|
1712
|
-
typeLabels
|
|
1713
|
-
.enter()
|
|
1714
|
-
.append("option")
|
|
1715
|
-
.attr("class", "seriesTypeLabel")
|
|
1716
|
-
.merge(typeLabels)
|
|
1717
|
-
.property("selected", (data) => {
|
|
1718
|
-
return ((data.type == Utils.getAgVisibleMeasure(self.chartComponentData.displayState, data.aggKey, data.splitBy)) ?
|
|
1719
|
-
" selected" : "");
|
|
1720
|
-
})
|
|
1721
|
-
.text((data) => data.type);
|
|
1722
|
-
typeLabels.exit().remove();
|
|
1723
|
-
});
|
|
1652
|
+
self.addSeriesTypeSelection(selection, aggKey, splitBy);
|
|
1724
1653
|
}
|
|
1725
1654
|
else {
|
|
1726
|
-
|
|
1655
|
+
selection.selectAll('.tsi-seriesTypeSelection').remove();
|
|
1727
1656
|
}
|
|
1728
1657
|
});
|
|
1729
1658
|
splitByLabels.exit().remove();
|
|
1730
|
-
|
|
1659
|
+
// Show more button
|
|
1660
|
+
const shouldShowMore = self.chartComponentData.displayState[aggKey].shownSplitBys < splitByLabelData.length;
|
|
1731
1661
|
splitByContainerEntered.selectAll('.tsi-legendShowMore').remove();
|
|
1732
1662
|
if (this.legendState === 'shown' && shouldShowMore) {
|
|
1733
1663
|
splitByContainerEntered.append('button')
|
|
1734
1664
|
.text(this.getString('Show more'))
|
|
1735
1665
|
.attr('class', 'tsi-legendShowMore')
|
|
1736
|
-
.style('display',
|
|
1666
|
+
.style('display', 'block')
|
|
1737
1667
|
.on('click', showMoreSplitBys);
|
|
1738
1668
|
}
|
|
1669
|
+
// Scroll handler for infinite scrolling
|
|
1739
1670
|
splitByContainerEntered.on("scroll", function () {
|
|
1740
1671
|
if (self.chartOptions.legend === 'shown') {
|
|
1741
|
-
if (this.scrollTop + this.clientHeight +
|
|
1672
|
+
if (this.scrollTop + this.clientHeight + LEGEND_CONSTANTS.SCROLL_BUFFER > this.scrollHeight) {
|
|
1742
1673
|
showMoreSplitBys();
|
|
1743
1674
|
}
|
|
1744
1675
|
}
|
|
@@ -1763,10 +1694,125 @@ class Legend extends Component {
|
|
|
1763
1694
|
};
|
|
1764
1695
|
this.drawChart = drawChart;
|
|
1765
1696
|
this.legendWidth = legendWidth;
|
|
1766
|
-
this.legendElement = d3__namespace.select(renderTarget)
|
|
1697
|
+
this.legendElement = d3__namespace.select(renderTarget)
|
|
1698
|
+
.insert("div", ":first-child")
|
|
1767
1699
|
.attr("class", "tsi-legend")
|
|
1768
|
-
.style("left", "0px")
|
|
1769
|
-
|
|
1700
|
+
.style("left", "0px");
|
|
1701
|
+
// Note: width is set conditionally in draw() based on legendState
|
|
1702
|
+
// to allow CSS to control width in compact mode
|
|
1703
|
+
}
|
|
1704
|
+
getHeightPerSplitBy(aggKey) {
|
|
1705
|
+
const dataType = this.chartComponentData.displayState[aggKey].dataType;
|
|
1706
|
+
return dataType === DataTypes.Numeric
|
|
1707
|
+
? LEGEND_CONSTANTS.NUMERIC_SPLITBY_HEIGHT
|
|
1708
|
+
: LEGEND_CONSTANTS.NON_NUMERIC_SPLITBY_HEIGHT;
|
|
1709
|
+
}
|
|
1710
|
+
addColorKey(selection, aggKey, splitBy, dataType) {
|
|
1711
|
+
const colors = Utils.createSplitByColors(this.chartComponentData.displayState, aggKey, this.chartOptions.keepSplitByColor);
|
|
1712
|
+
const splitByKeys = Object.keys(this.chartComponentData.timeArrays[aggKey]);
|
|
1713
|
+
const splitByIndex = splitByKeys.indexOf(splitBy);
|
|
1714
|
+
const color = this.chartComponentData.isFromHeatmap
|
|
1715
|
+
? this.chartComponentData.displayState[aggKey].color
|
|
1716
|
+
: colors[splitByIndex];
|
|
1717
|
+
const colorKey = selection.selectAll('.tsi-colorKey').data([color]);
|
|
1718
|
+
const colorKeyEntered = colorKey.enter()
|
|
1719
|
+
.append('div')
|
|
1720
|
+
.attr('class', 'tsi-colorKey')
|
|
1721
|
+
.merge(colorKey);
|
|
1722
|
+
if (dataType === DataTypes.Numeric) {
|
|
1723
|
+
colorKeyEntered.style('background-color', d => d);
|
|
1724
|
+
}
|
|
1725
|
+
else {
|
|
1726
|
+
this.createNonNumericColorKey(dataType, colorKeyEntered, aggKey);
|
|
1727
|
+
}
|
|
1728
|
+
colorKey.exit().remove();
|
|
1729
|
+
}
|
|
1730
|
+
addEyeIcon(selection, aggKey, splitBy) {
|
|
1731
|
+
if (selection.select('.tsi-eyeIcon').empty()) {
|
|
1732
|
+
selection.append('button')
|
|
1733
|
+
.attr('class', 'tsi-eyeIcon')
|
|
1734
|
+
.attr('aria-label', () => {
|
|
1735
|
+
const showOrHide = this.chartComponentData.displayState[aggKey].splitBys[splitBy].visible
|
|
1736
|
+
? this.getString('hide series')
|
|
1737
|
+
: this.getString('show series');
|
|
1738
|
+
return `${showOrHide} ${splitBy} ${this.getString('in group')} ${this.chartComponentData.displayState[aggKey].name}`;
|
|
1739
|
+
})
|
|
1740
|
+
.attr('title', () => this.getString('Show/Hide values'))
|
|
1741
|
+
.on('click', (event) => {
|
|
1742
|
+
event.stopPropagation();
|
|
1743
|
+
this.toggleSplitByVisible(aggKey, splitBy);
|
|
1744
|
+
this.drawChart();
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
selection.select('.tsi-eyeIcon')
|
|
1748
|
+
.classed('shown', Utils.getAgVisible(this.chartComponentData.displayState, aggKey, splitBy));
|
|
1749
|
+
}
|
|
1750
|
+
addSeriesName(selection, aggKey, splitBy) {
|
|
1751
|
+
if (selection.select('.tsi-seriesName').empty()) {
|
|
1752
|
+
const seriesName = selection.append('div')
|
|
1753
|
+
.attr('class', 'tsi-seriesName');
|
|
1754
|
+
const noSplitBys = Object.keys(this.chartComponentData.timeArrays[aggKey]).length === 1
|
|
1755
|
+
&& Object.keys(this.chartComponentData.timeArrays[aggKey])[0] === '';
|
|
1756
|
+
const displayText = noSplitBys
|
|
1757
|
+
? this.chartComponentData.displayState[aggKey].name
|
|
1758
|
+
: splitBy;
|
|
1759
|
+
Utils.appendFormattedElementsFromString(seriesName, displayText);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
addSeriesTypeSelection(selection, aggKey, splitBy) {
|
|
1763
|
+
if (selection.select('.tsi-seriesTypeSelection').empty()) {
|
|
1764
|
+
selection.append('select')
|
|
1765
|
+
.attr('aria-label', `${this.getString('Series type selection for')} ${splitBy} ${this.getString('in group')} ${this.chartComponentData.displayState[aggKey].name}`)
|
|
1766
|
+
.attr('class', 'tsi-seriesTypeSelection')
|
|
1767
|
+
.on('change', (event) => {
|
|
1768
|
+
const seriesType = d3__namespace.select(event.target).property('value');
|
|
1769
|
+
this.chartComponentData.displayState[aggKey].splitBys[splitBy].visibleType = seriesType;
|
|
1770
|
+
this.drawChart();
|
|
1771
|
+
})
|
|
1772
|
+
.on('click', (event) => {
|
|
1773
|
+
event.stopPropagation();
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
selection.select('.tsi-seriesTypeSelection')
|
|
1777
|
+
.each((d, i, nodes) => {
|
|
1778
|
+
const typeLabels = d3__namespace.select(nodes[i])
|
|
1779
|
+
.selectAll('option')
|
|
1780
|
+
.data(this.chartComponentData.displayState[aggKey].splitBys[splitBy].types.map(type => ({
|
|
1781
|
+
type,
|
|
1782
|
+
aggKey,
|
|
1783
|
+
splitBy,
|
|
1784
|
+
visibleMeasure: Utils.getAgVisibleMeasure(this.chartComponentData.displayState, aggKey, splitBy)
|
|
1785
|
+
})));
|
|
1786
|
+
typeLabels.enter()
|
|
1787
|
+
.append('option')
|
|
1788
|
+
.attr('class', 'seriesTypeLabel')
|
|
1789
|
+
.merge(typeLabels)
|
|
1790
|
+
.property('selected', (data) => data.type === Utils.getAgVisibleMeasure(this.chartComponentData.displayState, data.aggKey, data.splitBy))
|
|
1791
|
+
.text((data) => data.type);
|
|
1792
|
+
typeLabels.exit().remove();
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
handleSplitByClick(aggKey, splitBy) {
|
|
1796
|
+
if (this.legendState === 'compact') {
|
|
1797
|
+
this.toggleSplitByVisible(aggKey, splitBy);
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
this.toggleSticky(aggKey, splitBy);
|
|
1801
|
+
}
|
|
1802
|
+
this.drawChart();
|
|
1803
|
+
}
|
|
1804
|
+
handleSplitByMouseOver(aggKey, splitBy) {
|
|
1805
|
+
this.labelMouseover(aggKey, splitBy);
|
|
1806
|
+
}
|
|
1807
|
+
handleSplitByMouseOut(aggKey) {
|
|
1808
|
+
this.svgSelection.selectAll(".tsi-valueElement")
|
|
1809
|
+
.attr("stroke-opacity", 1)
|
|
1810
|
+
.attr("fill-opacity", 1);
|
|
1811
|
+
this.labelMouseout(this.svgSelection, aggKey);
|
|
1812
|
+
}
|
|
1813
|
+
isStickied(aggKey, splitBy) {
|
|
1814
|
+
const stickied = this.chartComponentData.stickiedKey;
|
|
1815
|
+
return stickied?.aggregateKey === aggKey && stickied?.splitBy === splitBy;
|
|
1770
1816
|
}
|
|
1771
1817
|
labelMouseoutWrapper(labelMouseout, svgSelection, event) {
|
|
1772
1818
|
return (svgSelection, aggKey) => {
|
|
@@ -1808,14 +1854,11 @@ class Legend extends Component {
|
|
|
1808
1854
|
return d == aggKey;
|
|
1809
1855
|
}).node();
|
|
1810
1856
|
var prospectiveScrollTop = Math.max((indexOfSplitBy - 1) * this.getHeightPerSplitBy(aggKey), 0);
|
|
1811
|
-
if (splitByNode.scrollTop < prospectiveScrollTop - (splitByNode.clientHeight -
|
|
1857
|
+
if (splitByNode.scrollTop < prospectiveScrollTop - (splitByNode.clientHeight - LEGEND_CONSTANTS.SCROLL_BUFFER) || splitByNode.scrollTop > prospectiveScrollTop) {
|
|
1812
1858
|
splitByNode.scrollTop = prospectiveScrollTop;
|
|
1813
1859
|
}
|
|
1814
1860
|
}
|
|
1815
1861
|
}
|
|
1816
|
-
getHeightPerSplitBy(aggKey) {
|
|
1817
|
-
return (this.chartComponentData.displayState[aggKey].dataType === DataTypes.Numeric ? NUMERICSPLITBYHEIGHT : NONNUMERICSPLITBYHEIGHT);
|
|
1818
|
-
}
|
|
1819
1862
|
createGradient(gradientKey, svg, values) {
|
|
1820
1863
|
let gradient = svg.append('defs').append('linearGradient')
|
|
1821
1864
|
.attr('id', gradientKey).attr('x1', '0%').attr('x2', '0%').attr('y1', '0%').attr('y2', '100%');
|
|
@@ -1834,10 +1877,6 @@ class Legend extends Component {
|
|
|
1834
1877
|
.attr("stop-opacity", 1);
|
|
1835
1878
|
});
|
|
1836
1879
|
}
|
|
1837
|
-
isNonNumeric(aggKey) {
|
|
1838
|
-
let dataType = this.chartComponentData.displayState[aggKey].dataType;
|
|
1839
|
-
return (dataType === DataTypes.Categorical || dataType === DataTypes.Events);
|
|
1840
|
-
}
|
|
1841
1880
|
createNonNumericColorKey(dataType, colorKey, aggKey) {
|
|
1842
1881
|
if (dataType === DataTypes.Categorical) {
|
|
1843
1882
|
this.createCategoricalColorKey(colorKey, aggKey);
|
|
@@ -1893,6 +1932,13 @@ class Legend extends Component {
|
|
|
1893
1932
|
rect.attr('fill', "url(#" + gradientKey + ")");
|
|
1894
1933
|
}
|
|
1895
1934
|
}
|
|
1935
|
+
handleShowMoreSplitBys(aggKey, splitByLabelData, aggSelection, dataType, noSplitBys) {
|
|
1936
|
+
const oldShownSplitBys = this.chartComponentData.displayState[aggKey].shownSplitBys;
|
|
1937
|
+
this.chartComponentData.displayState[aggKey].shownSplitBys = Math.min(oldShownSplitBys + LEGEND_CONSTANTS.BATCH_SIZE, splitByLabelData.length);
|
|
1938
|
+
if (oldShownSplitBys !== this.chartComponentData.displayState[aggKey].shownSplitBys) {
|
|
1939
|
+
this.renderSplitBys(aggKey, aggSelection, dataType, noSplitBys);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1896
1942
|
draw(legendState, chartComponentData, labelMouseover, svgSelection, options, labelMouseoutAction = null, stickySeriesAction = null, event) {
|
|
1897
1943
|
this.chartOptions.setOptions(options);
|
|
1898
1944
|
this.chartComponentData = chartComponentData;
|
|
@@ -1907,6 +1953,13 @@ class Legend extends Component {
|
|
|
1907
1953
|
legend.style('visibility', this.legendState != 'hidden')
|
|
1908
1954
|
.classed('compact', this.legendState == 'compact')
|
|
1909
1955
|
.classed('hidden', this.legendState == 'hidden');
|
|
1956
|
+
// Set width conditionally - let CSS handle compact mode width
|
|
1957
|
+
if (this.legendState !== 'compact') {
|
|
1958
|
+
legend.style('width', `${this.legendWidth}px`);
|
|
1959
|
+
}
|
|
1960
|
+
else {
|
|
1961
|
+
legend.style('width', null); // Remove inline width style in compact mode
|
|
1962
|
+
}
|
|
1910
1963
|
let seriesNames = Object.keys(this.chartComponentData.displayState);
|
|
1911
1964
|
var seriesLabels = legend.selectAll(".tsi-seriesLabel")
|
|
1912
1965
|
.data(seriesNames, d => d);
|
|
@@ -1917,7 +1970,7 @@ class Legend extends Component {
|
|
|
1917
1970
|
return "tsi-seriesLabel " + (this.chartComponentData.displayState[d]["visible"] ? " shown" : "");
|
|
1918
1971
|
})
|
|
1919
1972
|
.style("min-width", () => {
|
|
1920
|
-
return Math.min(
|
|
1973
|
+
return Math.min(LEGEND_CONSTANTS.MIN_SERIES_WIDTH, this.legendElement.node().clientWidth / seriesNames.length) + 'px';
|
|
1921
1974
|
})
|
|
1922
1975
|
.style("border-color", function (d, i) {
|
|
1923
1976
|
if (d3__namespace.select(this).classed("shown"))
|
|
@@ -1925,9 +1978,8 @@ class Legend extends Component {
|
|
|
1925
1978
|
return "lightgray";
|
|
1926
1979
|
});
|
|
1927
1980
|
var self = this;
|
|
1928
|
-
const heightPerNameLabel = 25;
|
|
1929
1981
|
const usableLegendHeight = legend.node().clientHeight;
|
|
1930
|
-
var prospectiveAggregateHeight = Math.ceil(Math.max(
|
|
1982
|
+
var prospectiveAggregateHeight = Math.ceil(Math.max(LEGEND_CONSTANTS.MIN_AGGREGATE_HEIGHT, (usableLegendHeight / seriesLabelsEntered.size())));
|
|
1931
1983
|
var contentHeight = 0;
|
|
1932
1984
|
seriesLabelsEntered.each(function (aggKey, i) {
|
|
1933
1985
|
let heightPerSplitBy = self.getHeightPerSplitBy(aggKey);
|
|
@@ -1983,12 +2035,12 @@ class Legend extends Component {
|
|
|
1983
2035
|
seriesNameLabel.exit().remove();
|
|
1984
2036
|
var splitByContainerHeight;
|
|
1985
2037
|
if (splitByLabelData.length > (prospectiveAggregateHeight / heightPerSplitBy)) {
|
|
1986
|
-
splitByContainerHeight = prospectiveAggregateHeight -
|
|
1987
|
-
contentHeight += splitByContainerHeight +
|
|
2038
|
+
splitByContainerHeight = prospectiveAggregateHeight - LEGEND_CONSTANTS.NAME_LABEL_HEIGHT;
|
|
2039
|
+
contentHeight += splitByContainerHeight + LEGEND_CONSTANTS.NAME_LABEL_HEIGHT;
|
|
1988
2040
|
}
|
|
1989
2041
|
else if (splitByLabelData.length > 1 || (splitByLabelData.length === 1 && splitByLabelData[0] !== "")) {
|
|
1990
|
-
splitByContainerHeight = splitByLabelData.length * heightPerSplitBy +
|
|
1991
|
-
contentHeight += splitByContainerHeight +
|
|
2042
|
+
splitByContainerHeight = splitByLabelData.length * heightPerSplitBy + LEGEND_CONSTANTS.NAME_LABEL_HEIGHT;
|
|
2043
|
+
contentHeight += splitByContainerHeight + LEGEND_CONSTANTS.NAME_LABEL_HEIGHT;
|
|
1992
2044
|
}
|
|
1993
2045
|
else {
|
|
1994
2046
|
splitByContainerHeight = heightPerSplitBy;
|
|
@@ -2001,43 +2053,28 @@ class Legend extends Component {
|
|
|
2001
2053
|
d3__namespace.select(this).style("height", "unset");
|
|
2002
2054
|
}
|
|
2003
2055
|
var splitByContainer = d3__namespace.select(this).selectAll(".tsi-splitByContainer").data([aggKey]);
|
|
2004
|
-
|
|
2056
|
+
splitByContainer.enter().append("div")
|
|
2005
2057
|
.merge(splitByContainer)
|
|
2006
2058
|
.classed("tsi-splitByContainer", true);
|
|
2007
2059
|
let aggSelection = d3__namespace.select(this);
|
|
2008
2060
|
self.renderSplitBys(aggKey, aggSelection, dataType, noSplitBys);
|
|
2009
|
-
|
|
2010
|
-
if (self.chartOptions.legend == "shown") {
|
|
2011
|
-
if (this.scrollTop + this.clientHeight + 40 > this.scrollHeight) {
|
|
2012
|
-
const oldShownSplitBys = self.chartComponentData.displayState[aggKey].shownSplitBys;
|
|
2013
|
-
self.chartComponentData.displayState[aggKey].shownSplitBys = Math.min(oldShownSplitBys + 20, splitByLabelData.length);
|
|
2014
|
-
if (oldShownSplitBys != self.chartComponentData.displayState[aggKey].shownSplitBys) {
|
|
2015
|
-
self.renderSplitBys(aggKey, aggSelection, dataType, noSplitBys);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
});
|
|
2061
|
+
// Compact mode horizontal scroll handler
|
|
2020
2062
|
d3__namespace.select(this).on('scroll', function () {
|
|
2021
2063
|
if (self.chartOptions.legend == "compact") {
|
|
2022
|
-
if (this.scrollLeft + this.clientWidth +
|
|
2023
|
-
|
|
2024
|
-
self.chartComponentData.displayState[aggKey].shownSplitBys = Math.min(oldShownSplitBys + 20, splitByLabelData.length);
|
|
2025
|
-
if (oldShownSplitBys != self.chartComponentData.displayState[aggKey].shownSplitBys) {
|
|
2026
|
-
this.renderSplitBys(dataType);
|
|
2027
|
-
}
|
|
2064
|
+
if (this.scrollLeft + this.clientWidth + LEGEND_CONSTANTS.SCROLL_BUFFER > this.scrollWidth) {
|
|
2065
|
+
self.handleShowMoreSplitBys(aggKey, splitByLabelData, aggSelection, dataType, noSplitBys);
|
|
2028
2066
|
}
|
|
2029
2067
|
}
|
|
2030
2068
|
});
|
|
2031
2069
|
splitByContainer.exit().remove();
|
|
2032
2070
|
});
|
|
2033
2071
|
if (this.chartOptions.legend == 'shown') {
|
|
2034
|
-
legend.node().clientHeight;
|
|
2035
2072
|
//minSplitBysForFlexGrow: the minimum number of split bys for flex-grow to be triggered
|
|
2036
2073
|
if (contentHeight < usableLegendHeight) {
|
|
2037
2074
|
this.legendElement.classed("tsi-flexLegend", true);
|
|
2038
2075
|
seriesLabelsEntered.each(function (d) {
|
|
2039
2076
|
let heightPerSplitBy = self.getHeightPerSplitBy(d);
|
|
2040
|
-
var minSplitByForFlexGrow = (prospectiveAggregateHeight -
|
|
2077
|
+
var minSplitByForFlexGrow = (prospectiveAggregateHeight - LEGEND_CONSTANTS.NAME_LABEL_HEIGHT) / heightPerSplitBy;
|
|
2041
2078
|
var splitBysCount = Object.keys(self.chartComponentData.displayState[String(d3__namespace.select(this).data()[0])].splitBys).length;
|
|
2042
2079
|
if (splitBysCount > minSplitByForFlexGrow) {
|
|
2043
2080
|
d3__namespace.select(this).style("flex-grow", 1);
|
|
@@ -2050,6 +2087,12 @@ class Legend extends Component {
|
|
|
2050
2087
|
}
|
|
2051
2088
|
seriesLabels.exit().remove();
|
|
2052
2089
|
}
|
|
2090
|
+
destroy() {
|
|
2091
|
+
this.legendElement.remove();
|
|
2092
|
+
// Note: Virtual list cleanup will be added when virtual scrolling is implemented
|
|
2093
|
+
// this.virtualLists.forEach(list => list.destroy());
|
|
2094
|
+
// this.virtualLists.clear();
|
|
2095
|
+
}
|
|
2053
2096
|
}
|
|
2054
2097
|
|
|
2055
2098
|
class ChartComponentData {
|
|
@@ -6325,6 +6368,8 @@ class LineChart extends TemporalXAxisComponent {
|
|
|
6325
6368
|
.append("text")
|
|
6326
6369
|
.attr("class", (d) => `tsi-swimLaneLabel-${lane} tsi-swimLaneLabel ${onClickPresentAndValid(d) ? 'tsi-boldOnHover' : ''}`)
|
|
6327
6370
|
.attr("role", "heading")
|
|
6371
|
+
.attr("aria-roledescription", this.getString("Swimlane label"))
|
|
6372
|
+
.attr("aria-label", d => d.label)
|
|
6328
6373
|
.attr("aria-level", "3")
|
|
6329
6374
|
.merge(label)
|
|
6330
6375
|
.style("text-anchor", "middle")
|
|
@@ -6863,1205 +6908,6 @@ class TimezonePicker extends ChartComponent {
|
|
|
6863
6908
|
}
|
|
6864
6909
|
}
|
|
6865
6910
|
|
|
6866
|
-
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
6867
|
-
|
|
6868
|
-
function getDefaultExportFromCjs (x) {
|
|
6869
|
-
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
6870
|
-
}
|
|
6871
|
-
|
|
6872
|
-
var pikaday$1 = {exports: {}};
|
|
6873
|
-
|
|
6874
|
-
/*!
|
|
6875
|
-
* Pikaday
|
|
6876
|
-
*
|
|
6877
|
-
* Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
|
|
6878
|
-
*/
|
|
6879
|
-
var pikaday = pikaday$1.exports;
|
|
6880
|
-
|
|
6881
|
-
var hasRequiredPikaday;
|
|
6882
|
-
|
|
6883
|
-
function requirePikaday () {
|
|
6884
|
-
if (hasRequiredPikaday) return pikaday$1.exports;
|
|
6885
|
-
hasRequiredPikaday = 1;
|
|
6886
|
-
(function (module, exports) {
|
|
6887
|
-
(function (root, factory) {
|
|
6888
|
-
|
|
6889
|
-
var moment;
|
|
6890
|
-
{
|
|
6891
|
-
// CommonJS module
|
|
6892
|
-
// Load moment.js as an optional dependency
|
|
6893
|
-
try { moment = require('moment'); } catch (e) { moment = (typeof window !== 'undefined' && window.moment) || undefined; }
|
|
6894
|
-
module.exports = factory(moment);
|
|
6895
|
-
}
|
|
6896
|
-
}(typeof self !== 'undefined' ? self :
|
|
6897
|
-
typeof window !== 'undefined' ? window :
|
|
6898
|
-
typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
|
|
6899
|
-
pikaday, function (moment) {
|
|
6900
|
-
|
|
6901
|
-
/**
|
|
6902
|
-
* feature detection and helper functions
|
|
6903
|
-
*/
|
|
6904
|
-
var hasMoment = typeof moment === 'function' || (moment && typeof moment.version === 'string'),
|
|
6905
|
-
|
|
6906
|
-
hasEventListeners = !!window.addEventListener,
|
|
6907
|
-
|
|
6908
|
-
document = window.document,
|
|
6909
|
-
|
|
6910
|
-
sto = window.setTimeout,
|
|
6911
|
-
|
|
6912
|
-
addEvent = function (el, e, callback, capture) {
|
|
6913
|
-
if (hasEventListeners) {
|
|
6914
|
-
el.addEventListener(e, callback, !!capture);
|
|
6915
|
-
} else {
|
|
6916
|
-
el.attachEvent('on' + e, callback);
|
|
6917
|
-
}
|
|
6918
|
-
},
|
|
6919
|
-
|
|
6920
|
-
removeEvent = function (el, e, callback, capture) {
|
|
6921
|
-
if (hasEventListeners) {
|
|
6922
|
-
el.removeEventListener(e, callback, !!capture);
|
|
6923
|
-
} else {
|
|
6924
|
-
el.detachEvent('on' + e, callback);
|
|
6925
|
-
}
|
|
6926
|
-
},
|
|
6927
|
-
|
|
6928
|
-
trim = function (str) {
|
|
6929
|
-
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
|
|
6930
|
-
},
|
|
6931
|
-
|
|
6932
|
-
hasClass = function (el, cn) {
|
|
6933
|
-
return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
|
|
6934
|
-
},
|
|
6935
|
-
|
|
6936
|
-
addClass = function (el, cn) {
|
|
6937
|
-
if (!hasClass(el, cn)) {
|
|
6938
|
-
el.className = (el.className === '') ? cn : el.className + ' ' + cn;
|
|
6939
|
-
}
|
|
6940
|
-
},
|
|
6941
|
-
|
|
6942
|
-
removeClass = function (el, cn) {
|
|
6943
|
-
el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
|
|
6944
|
-
},
|
|
6945
|
-
|
|
6946
|
-
isArray = function (obj) {
|
|
6947
|
-
return (/Array/).test(Object.prototype.toString.call(obj));
|
|
6948
|
-
},
|
|
6949
|
-
|
|
6950
|
-
isDate = function (obj) {
|
|
6951
|
-
return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
|
|
6952
|
-
},
|
|
6953
|
-
|
|
6954
|
-
isWeekend = function (date) {
|
|
6955
|
-
var day = date.getDay();
|
|
6956
|
-
return day === 0 || day === 6;
|
|
6957
|
-
},
|
|
6958
|
-
|
|
6959
|
-
isLeapYear = function (year) {
|
|
6960
|
-
// solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
|
|
6961
|
-
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
6962
|
-
},
|
|
6963
|
-
|
|
6964
|
-
getDaysInMonth = function (year, month) {
|
|
6965
|
-
return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
6966
|
-
},
|
|
6967
|
-
|
|
6968
|
-
setToStartOfDay = function (date) {
|
|
6969
|
-
if (isDate(date)) date.setHours(0, 0, 0, 0);
|
|
6970
|
-
},
|
|
6971
|
-
|
|
6972
|
-
compareDates = function (a, b) {
|
|
6973
|
-
// weak date comparison (use setToStartOfDay(date) to ensure correct result)
|
|
6974
|
-
return a.getTime() === b.getTime();
|
|
6975
|
-
},
|
|
6976
|
-
|
|
6977
|
-
extend = function (to, from, overwrite) {
|
|
6978
|
-
var prop, hasProp;
|
|
6979
|
-
for (prop in from) {
|
|
6980
|
-
hasProp = to[prop] !== undefined;
|
|
6981
|
-
if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
|
|
6982
|
-
if (isDate(from[prop])) {
|
|
6983
|
-
if (overwrite) {
|
|
6984
|
-
to[prop] = new Date(from[prop].getTime());
|
|
6985
|
-
}
|
|
6986
|
-
}
|
|
6987
|
-
else if (isArray(from[prop])) {
|
|
6988
|
-
if (overwrite) {
|
|
6989
|
-
to[prop] = from[prop].slice(0);
|
|
6990
|
-
}
|
|
6991
|
-
} else {
|
|
6992
|
-
to[prop] = extend({}, from[prop], overwrite);
|
|
6993
|
-
}
|
|
6994
|
-
} else if (overwrite || !hasProp) {
|
|
6995
|
-
to[prop] = from[prop];
|
|
6996
|
-
}
|
|
6997
|
-
}
|
|
6998
|
-
return to;
|
|
6999
|
-
},
|
|
7000
|
-
|
|
7001
|
-
fireEvent = function (el, eventName, data) {
|
|
7002
|
-
var ev;
|
|
7003
|
-
|
|
7004
|
-
if (document.createEvent) {
|
|
7005
|
-
ev = document.createEvent('HTMLEvents');
|
|
7006
|
-
ev.initEvent(eventName, true, false);
|
|
7007
|
-
ev = extend(ev, data);
|
|
7008
|
-
el.dispatchEvent(ev);
|
|
7009
|
-
} else if (document.createEventObject) {
|
|
7010
|
-
ev = document.createEventObject();
|
|
7011
|
-
ev = extend(ev, data);
|
|
7012
|
-
el.fireEvent('on' + eventName, ev);
|
|
7013
|
-
}
|
|
7014
|
-
},
|
|
7015
|
-
|
|
7016
|
-
adjustCalendar = function (calendar) {
|
|
7017
|
-
if (calendar.month < 0) {
|
|
7018
|
-
calendar.year -= Math.ceil(Math.abs(calendar.month) / 12);
|
|
7019
|
-
calendar.month += 12;
|
|
7020
|
-
}
|
|
7021
|
-
if (calendar.month > 11) {
|
|
7022
|
-
calendar.year += Math.floor(Math.abs(calendar.month) / 12);
|
|
7023
|
-
calendar.month -= 12;
|
|
7024
|
-
}
|
|
7025
|
-
return calendar;
|
|
7026
|
-
},
|
|
7027
|
-
|
|
7028
|
-
/**
|
|
7029
|
-
* defaults and localisation
|
|
7030
|
-
*/
|
|
7031
|
-
defaults = {
|
|
7032
|
-
|
|
7033
|
-
// bind the picker to a form field
|
|
7034
|
-
field: null,
|
|
7035
|
-
|
|
7036
|
-
// automatically show/hide the picker on `field` focus (default `true` if `field` is set)
|
|
7037
|
-
bound: undefined,
|
|
7038
|
-
|
|
7039
|
-
// position of the datepicker, relative to the field (default to bottom & left)
|
|
7040
|
-
// ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
|
|
7041
|
-
position: 'bottom left',
|
|
7042
|
-
|
|
7043
|
-
// automatically fit in the viewport even if it means repositioning from the position option
|
|
7044
|
-
reposition: true,
|
|
7045
|
-
|
|
7046
|
-
// the default output format for `.toString()` and `field` value
|
|
7047
|
-
format: 'YYYY-MM-DD',
|
|
7048
|
-
|
|
7049
|
-
// the toString function which gets passed a current date object and format
|
|
7050
|
-
// and returns a string
|
|
7051
|
-
toString: null,
|
|
7052
|
-
|
|
7053
|
-
// used to create date object from current input string
|
|
7054
|
-
parse: null,
|
|
7055
|
-
|
|
7056
|
-
// the initial date to view when first opened
|
|
7057
|
-
defaultDate: null,
|
|
7058
|
-
|
|
7059
|
-
// make the `defaultDate` the initial selected value
|
|
7060
|
-
setDefaultDate: false,
|
|
7061
|
-
|
|
7062
|
-
// first day of week (0: Sunday, 1: Monday etc)
|
|
7063
|
-
firstDay: 0,
|
|
7064
|
-
|
|
7065
|
-
// the default flag for moment's strict date parsing
|
|
7066
|
-
formatStrict: false,
|
|
7067
|
-
|
|
7068
|
-
// the minimum/earliest date that can be selected
|
|
7069
|
-
minDate: null,
|
|
7070
|
-
// the maximum/latest date that can be selected
|
|
7071
|
-
maxDate: null,
|
|
7072
|
-
|
|
7073
|
-
// number of years either side, or array of upper/lower range
|
|
7074
|
-
yearRange: 10,
|
|
7075
|
-
|
|
7076
|
-
// show week numbers at head of row
|
|
7077
|
-
showWeekNumber: false,
|
|
7078
|
-
|
|
7079
|
-
// Week picker mode
|
|
7080
|
-
pickWholeWeek: false,
|
|
7081
|
-
|
|
7082
|
-
// used internally (don't config outside)
|
|
7083
|
-
minYear: 0,
|
|
7084
|
-
maxYear: 9999,
|
|
7085
|
-
minMonth: undefined,
|
|
7086
|
-
maxMonth: undefined,
|
|
7087
|
-
|
|
7088
|
-
startRange: null,
|
|
7089
|
-
endRange: null,
|
|
7090
|
-
|
|
7091
|
-
isRTL: false,
|
|
7092
|
-
|
|
7093
|
-
// Additional text to append to the year in the calendar title
|
|
7094
|
-
yearSuffix: '',
|
|
7095
|
-
|
|
7096
|
-
// Render the month after year in the calendar title
|
|
7097
|
-
showMonthAfterYear: false,
|
|
7098
|
-
|
|
7099
|
-
// Render days of the calendar grid that fall in the next or previous month
|
|
7100
|
-
showDaysInNextAndPreviousMonths: false,
|
|
7101
|
-
|
|
7102
|
-
// Allows user to select days that fall in the next or previous month
|
|
7103
|
-
enableSelectionDaysInNextAndPreviousMonths: false,
|
|
7104
|
-
|
|
7105
|
-
// how many months are visible
|
|
7106
|
-
numberOfMonths: 1,
|
|
7107
|
-
|
|
7108
|
-
// when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
|
|
7109
|
-
// only used for the first display or when a selected date is not visible
|
|
7110
|
-
mainCalendar: 'left',
|
|
7111
|
-
|
|
7112
|
-
// Specify a DOM element to render the calendar in
|
|
7113
|
-
container: undefined,
|
|
7114
|
-
|
|
7115
|
-
// Blur field when date is selected
|
|
7116
|
-
blurFieldOnSelect: true,
|
|
7117
|
-
|
|
7118
|
-
// internationalization
|
|
7119
|
-
i18n: {
|
|
7120
|
-
previousMonth: 'Previous Month',
|
|
7121
|
-
nextMonth: 'Next Month',
|
|
7122
|
-
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
7123
|
-
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
7124
|
-
weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
7125
|
-
},
|
|
7126
|
-
|
|
7127
|
-
// Theme Classname
|
|
7128
|
-
theme: null,
|
|
7129
|
-
|
|
7130
|
-
// events array
|
|
7131
|
-
events: [],
|
|
7132
|
-
|
|
7133
|
-
// callback function
|
|
7134
|
-
onSelect: null,
|
|
7135
|
-
onOpen: null,
|
|
7136
|
-
onClose: null,
|
|
7137
|
-
onDraw: null,
|
|
7138
|
-
|
|
7139
|
-
// Enable keyboard input
|
|
7140
|
-
keyboardInput: true
|
|
7141
|
-
},
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
/**
|
|
7145
|
-
* templating functions to abstract HTML rendering
|
|
7146
|
-
*/
|
|
7147
|
-
renderDayName = function (opts, day, abbr) {
|
|
7148
|
-
day += opts.firstDay;
|
|
7149
|
-
while (day >= 7) {
|
|
7150
|
-
day -= 7;
|
|
7151
|
-
}
|
|
7152
|
-
return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
|
|
7153
|
-
},
|
|
7154
|
-
|
|
7155
|
-
renderDay = function (opts) {
|
|
7156
|
-
var arr = [];
|
|
7157
|
-
var ariaSelected = 'false';
|
|
7158
|
-
if (opts.isEmpty) {
|
|
7159
|
-
if (opts.showDaysInNextAndPreviousMonths) {
|
|
7160
|
-
arr.push('is-outside-current-month');
|
|
7161
|
-
|
|
7162
|
-
if (!opts.enableSelectionDaysInNextAndPreviousMonths) {
|
|
7163
|
-
arr.push('is-selection-disabled');
|
|
7164
|
-
}
|
|
7165
|
-
|
|
7166
|
-
} else {
|
|
7167
|
-
return '<td class="is-empty"></td>';
|
|
7168
|
-
}
|
|
7169
|
-
}
|
|
7170
|
-
if (opts.isDisabled) {
|
|
7171
|
-
arr.push('is-disabled');
|
|
7172
|
-
}
|
|
7173
|
-
if (opts.isToday) {
|
|
7174
|
-
arr.push('is-today');
|
|
7175
|
-
}
|
|
7176
|
-
if (opts.isSelected) {
|
|
7177
|
-
arr.push('is-selected');
|
|
7178
|
-
ariaSelected = 'true';
|
|
7179
|
-
}
|
|
7180
|
-
if (opts.hasEvent) {
|
|
7181
|
-
arr.push('has-event');
|
|
7182
|
-
}
|
|
7183
|
-
if (opts.isInRange) {
|
|
7184
|
-
arr.push('is-inrange');
|
|
7185
|
-
}
|
|
7186
|
-
if (opts.isStartRange) {
|
|
7187
|
-
arr.push('is-startrange');
|
|
7188
|
-
}
|
|
7189
|
-
if (opts.isEndRange) {
|
|
7190
|
-
arr.push('is-endrange');
|
|
7191
|
-
}
|
|
7192
|
-
return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '" aria-selected="' + ariaSelected + '">' +
|
|
7193
|
-
'<button tabIndex="-1" class="pika-button pika-day" type="button" ' +
|
|
7194
|
-
'data-pika-year="' + opts.year + '" data-pika-month="' + opts.month + '" data-pika-day="' + opts.day + '">' +
|
|
7195
|
-
opts.day +
|
|
7196
|
-
'</button>' +
|
|
7197
|
-
'</td>';
|
|
7198
|
-
},
|
|
7199
|
-
|
|
7200
|
-
renderWeek = function (d, m, y) {
|
|
7201
|
-
// Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
|
|
7202
|
-
var onejan = new Date(y, 0, 1),
|
|
7203
|
-
weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay() + 1) / 7);
|
|
7204
|
-
return '<td class="pika-week">' + weekNum + '</td>';
|
|
7205
|
-
},
|
|
7206
|
-
|
|
7207
|
-
renderRow = function (days, isRTL, pickWholeWeek, isRowSelected) {
|
|
7208
|
-
return '<tr class="pika-row' + (pickWholeWeek ? ' pick-whole-week' : '') + (isRowSelected ? ' is-selected' : '') + '">' + (isRTL ? days.reverse() : days).join('') + '</tr>';
|
|
7209
|
-
},
|
|
7210
|
-
|
|
7211
|
-
renderBody = function (rows) {
|
|
7212
|
-
return '<tbody>' + rows.join('') + '</tbody>';
|
|
7213
|
-
},
|
|
7214
|
-
|
|
7215
|
-
renderHead = function (opts) {
|
|
7216
|
-
var i, arr = [];
|
|
7217
|
-
if (opts.showWeekNumber) {
|
|
7218
|
-
arr.push('<th></th>');
|
|
7219
|
-
}
|
|
7220
|
-
for (i = 0; i < 7; i++) {
|
|
7221
|
-
arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>');
|
|
7222
|
-
}
|
|
7223
|
-
return '<thead><tr>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</tr></thead>';
|
|
7224
|
-
},
|
|
7225
|
-
|
|
7226
|
-
renderTitle = function (instance, c, year, month, refYear, randId) {
|
|
7227
|
-
var i, j, arr,
|
|
7228
|
-
opts = instance._o,
|
|
7229
|
-
isMinYear = year === opts.minYear,
|
|
7230
|
-
isMaxYear = year === opts.maxYear,
|
|
7231
|
-
html = '<div id="' + randId + '" class="pika-title">',
|
|
7232
|
-
monthHtml,
|
|
7233
|
-
yearHtml,
|
|
7234
|
-
prev = true,
|
|
7235
|
-
next = true;
|
|
7236
|
-
|
|
7237
|
-
for (arr = [], i = 0; i < 12; i++) {
|
|
7238
|
-
arr.push('<option value="' + (year === refYear ? i - c : 12 + i - c) + '"' +
|
|
7239
|
-
(i === month ? ' selected="selected"' : '') +
|
|
7240
|
-
((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled="disabled"' : '') + '>' +
|
|
7241
|
-
opts.i18n.months[i] + '</option>');
|
|
7242
|
-
}
|
|
7243
|
-
|
|
7244
|
-
monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select aria-label="select month" class="pika-select pika-select-month" tabindex="-1">' + arr.join('') + '</select></div>';
|
|
7245
|
-
|
|
7246
|
-
if (isArray(opts.yearRange)) {
|
|
7247
|
-
i = opts.yearRange[0];
|
|
7248
|
-
j = opts.yearRange[1] + 1;
|
|
7249
|
-
} else {
|
|
7250
|
-
i = year - opts.yearRange;
|
|
7251
|
-
j = 1 + year + opts.yearRange;
|
|
7252
|
-
}
|
|
7253
|
-
|
|
7254
|
-
for (arr = []; i < j && i <= opts.maxYear; i++) {
|
|
7255
|
-
if (i >= opts.minYear) {
|
|
7256
|
-
arr.push('<option value="' + i + '"' + (i === year ? ' selected="selected"' : '') + '>' + (i) + '</option>');
|
|
7257
|
-
}
|
|
7258
|
-
}
|
|
7259
|
-
yearHtml = '<div class="pika-label">' + year + opts.yearSuffix + '<select aria-label="select year" class="pika-select pika-select-year" tabindex="-1">' + arr.join('') + '</select></div>';
|
|
7260
|
-
|
|
7261
|
-
if (opts.showMonthAfterYear) {
|
|
7262
|
-
html += yearHtml + monthHtml;
|
|
7263
|
-
} else {
|
|
7264
|
-
html += monthHtml + yearHtml;
|
|
7265
|
-
}
|
|
7266
|
-
|
|
7267
|
-
if (isMinYear && (month === 0 || opts.minMonth >= month)) {
|
|
7268
|
-
prev = false;
|
|
7269
|
-
}
|
|
7270
|
-
|
|
7271
|
-
if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
|
|
7272
|
-
next = false;
|
|
7273
|
-
}
|
|
7274
|
-
|
|
7275
|
-
if (c === 0) {
|
|
7276
|
-
html += '<button tabIndex="-1" class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>';
|
|
7277
|
-
}
|
|
7278
|
-
if (c === (instance._o.numberOfMonths - 1)) {
|
|
7279
|
-
html += '<button tabIndex="-1" class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>';
|
|
7280
|
-
}
|
|
7281
|
-
|
|
7282
|
-
return html += '</div>';
|
|
7283
|
-
},
|
|
7284
|
-
|
|
7285
|
-
renderTable = function (opts, data, randId) {
|
|
7286
|
-
return '<table cellpadding="0" cellspacing="0" class="pika-table" role="grid" aria-labelledby="' + randId + '">' + renderHead(opts) + renderBody(data) + '</table>';
|
|
7287
|
-
},
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
/**
|
|
7291
|
-
* Pikaday constructor
|
|
7292
|
-
*/
|
|
7293
|
-
Pikaday = function (options) {
|
|
7294
|
-
var self = this,
|
|
7295
|
-
opts = self.config(options);
|
|
7296
|
-
|
|
7297
|
-
self._onMouseDown = function (e) {
|
|
7298
|
-
if (!self._v) {
|
|
7299
|
-
return;
|
|
7300
|
-
}
|
|
7301
|
-
e = e || window.event;
|
|
7302
|
-
var target = e.target || e.srcElement;
|
|
7303
|
-
if (!target) {
|
|
7304
|
-
return;
|
|
7305
|
-
}
|
|
7306
|
-
|
|
7307
|
-
if (!hasClass(target, 'is-disabled')) {
|
|
7308
|
-
if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) {
|
|
7309
|
-
self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
|
|
7310
|
-
if (opts.bound) {
|
|
7311
|
-
sto(function () {
|
|
7312
|
-
self.hide();
|
|
7313
|
-
if (opts.blurFieldOnSelect && opts.field) {
|
|
7314
|
-
opts.field.blur();
|
|
7315
|
-
}
|
|
7316
|
-
}, 100);
|
|
7317
|
-
}
|
|
7318
|
-
}
|
|
7319
|
-
else if (hasClass(target, 'pika-prev')) {
|
|
7320
|
-
self.prevMonth();
|
|
7321
|
-
}
|
|
7322
|
-
else if (hasClass(target, 'pika-next')) {
|
|
7323
|
-
self.nextMonth();
|
|
7324
|
-
}
|
|
7325
|
-
}
|
|
7326
|
-
if (!hasClass(target, 'pika-select')) {
|
|
7327
|
-
// if this is touch event prevent mouse events emulation
|
|
7328
|
-
if (e.preventDefault) {
|
|
7329
|
-
e.preventDefault();
|
|
7330
|
-
} else {
|
|
7331
|
-
e.returnValue = false;
|
|
7332
|
-
return false;
|
|
7333
|
-
}
|
|
7334
|
-
} else {
|
|
7335
|
-
self._c = true;
|
|
7336
|
-
}
|
|
7337
|
-
};
|
|
7338
|
-
|
|
7339
|
-
self._onChange = function (e) {
|
|
7340
|
-
e = e || window.event;
|
|
7341
|
-
var target = e.target || e.srcElement;
|
|
7342
|
-
if (!target) {
|
|
7343
|
-
return;
|
|
7344
|
-
}
|
|
7345
|
-
if (hasClass(target, 'pika-select-month')) {
|
|
7346
|
-
self.gotoMonth(target.value);
|
|
7347
|
-
}
|
|
7348
|
-
else if (hasClass(target, 'pika-select-year')) {
|
|
7349
|
-
self.gotoYear(target.value);
|
|
7350
|
-
}
|
|
7351
|
-
};
|
|
7352
|
-
|
|
7353
|
-
self._onKeyChange = function (e) {
|
|
7354
|
-
e = e || window.event;
|
|
7355
|
-
// ignore if event comes from input box
|
|
7356
|
-
if (self.isVisible() && e.target && e.target.type !== 'text') {
|
|
7357
|
-
|
|
7358
|
-
switch (e.keyCode) {
|
|
7359
|
-
case 13:
|
|
7360
|
-
case 27:
|
|
7361
|
-
if (opts.field) {
|
|
7362
|
-
opts.field.blur();
|
|
7363
|
-
}
|
|
7364
|
-
break;
|
|
7365
|
-
case 37:
|
|
7366
|
-
e.preventDefault();
|
|
7367
|
-
self.adjustDate('subtract', 1);
|
|
7368
|
-
break;
|
|
7369
|
-
case 38:
|
|
7370
|
-
self.adjustDate('subtract', 7);
|
|
7371
|
-
break;
|
|
7372
|
-
case 39:
|
|
7373
|
-
self.adjustDate('add', 1);
|
|
7374
|
-
break;
|
|
7375
|
-
case 40:
|
|
7376
|
-
self.adjustDate('add', 7);
|
|
7377
|
-
break;
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
};
|
|
7381
|
-
|
|
7382
|
-
self._onInputChange = function (e) {
|
|
7383
|
-
var date;
|
|
7384
|
-
|
|
7385
|
-
if (e.firedBy === self) {
|
|
7386
|
-
return;
|
|
7387
|
-
}
|
|
7388
|
-
if (opts.parse) {
|
|
7389
|
-
date = opts.parse(opts.field.value, opts.format);
|
|
7390
|
-
} else if (hasMoment) {
|
|
7391
|
-
date = moment(opts.field.value, opts.format, opts.formatStrict);
|
|
7392
|
-
date = (date && date.isValid()) ? date.toDate() : null;
|
|
7393
|
-
}
|
|
7394
|
-
else {
|
|
7395
|
-
date = new Date(Date.parse(opts.field.value));
|
|
7396
|
-
}
|
|
7397
|
-
// if (isDate(date)) {
|
|
7398
|
-
// self.setDate(date);
|
|
7399
|
-
// }
|
|
7400
|
-
// if (!self._v) {
|
|
7401
|
-
// self.show();
|
|
7402
|
-
// }
|
|
7403
|
-
};
|
|
7404
|
-
|
|
7405
|
-
self._onInputFocus = function () {
|
|
7406
|
-
self.show();
|
|
7407
|
-
};
|
|
7408
|
-
|
|
7409
|
-
self._onInputClick = function () {
|
|
7410
|
-
self.show();
|
|
7411
|
-
};
|
|
7412
|
-
|
|
7413
|
-
self._onInputBlur = function () {
|
|
7414
|
-
// IE allows pika div to gain focus; catch blur the input field
|
|
7415
|
-
var pEl = document.activeElement;
|
|
7416
|
-
do {
|
|
7417
|
-
if (hasClass(pEl, 'pika-single')) {
|
|
7418
|
-
return;
|
|
7419
|
-
}
|
|
7420
|
-
}
|
|
7421
|
-
while ((pEl = pEl.parentNode));
|
|
7422
|
-
|
|
7423
|
-
if (!self._c) {
|
|
7424
|
-
self._b = sto(function () {
|
|
7425
|
-
self.hide();
|
|
7426
|
-
}, 50);
|
|
7427
|
-
}
|
|
7428
|
-
self._c = false;
|
|
7429
|
-
};
|
|
7430
|
-
|
|
7431
|
-
self._onClick = function (e) {
|
|
7432
|
-
e = e || window.event;
|
|
7433
|
-
var target = e.target || e.srcElement,
|
|
7434
|
-
pEl = target;
|
|
7435
|
-
if (!target) {
|
|
7436
|
-
return;
|
|
7437
|
-
}
|
|
7438
|
-
if (!hasEventListeners && hasClass(target, 'pika-select')) {
|
|
7439
|
-
if (!target.onchange) {
|
|
7440
|
-
target.setAttribute('onchange', 'return;');
|
|
7441
|
-
addEvent(target, 'change', self._onChange);
|
|
7442
|
-
}
|
|
7443
|
-
}
|
|
7444
|
-
do {
|
|
7445
|
-
if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
|
|
7446
|
-
return;
|
|
7447
|
-
}
|
|
7448
|
-
}
|
|
7449
|
-
while ((pEl = pEl.parentNode));
|
|
7450
|
-
if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
|
|
7451
|
-
self.hide();
|
|
7452
|
-
}
|
|
7453
|
-
};
|
|
7454
|
-
|
|
7455
|
-
self.el = document.createElement('div');
|
|
7456
|
-
self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
|
|
7457
|
-
|
|
7458
|
-
addEvent(self.el, 'mousedown', self._onMouseDown, true);
|
|
7459
|
-
addEvent(self.el, 'touchend', self._onMouseDown, true);
|
|
7460
|
-
addEvent(self.el, 'change', self._onChange);
|
|
7461
|
-
|
|
7462
|
-
if (opts.keyboardInput) {
|
|
7463
|
-
addEvent(document, 'keydown', self._onKeyChange);
|
|
7464
|
-
}
|
|
7465
|
-
|
|
7466
|
-
if (opts.field) {
|
|
7467
|
-
if (opts.container) {
|
|
7468
|
-
opts.container.appendChild(self.el);
|
|
7469
|
-
} else if (opts.bound) {
|
|
7470
|
-
document.body.appendChild(self.el);
|
|
7471
|
-
} else {
|
|
7472
|
-
opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
|
|
7473
|
-
}
|
|
7474
|
-
addEvent(opts.field, 'change', self._onInputChange);
|
|
7475
|
-
|
|
7476
|
-
if (!opts.defaultDate) {
|
|
7477
|
-
if (hasMoment && opts.field.value) {
|
|
7478
|
-
opts.defaultDate = moment(opts.field.value, opts.format).toDate();
|
|
7479
|
-
} else {
|
|
7480
|
-
opts.defaultDate = new Date(Date.parse(opts.field.value));
|
|
7481
|
-
}
|
|
7482
|
-
opts.setDefaultDate = true;
|
|
7483
|
-
}
|
|
7484
|
-
}
|
|
7485
|
-
|
|
7486
|
-
var defDate = opts.defaultDate;
|
|
7487
|
-
|
|
7488
|
-
if (isDate(defDate)) {
|
|
7489
|
-
if (opts.setDefaultDate) {
|
|
7490
|
-
self.setDate(defDate, true);
|
|
7491
|
-
} else {
|
|
7492
|
-
self.gotoDate(defDate);
|
|
7493
|
-
}
|
|
7494
|
-
} else {
|
|
7495
|
-
self.gotoDate(new Date());
|
|
7496
|
-
}
|
|
7497
|
-
|
|
7498
|
-
if (opts.bound) {
|
|
7499
|
-
this.hide();
|
|
7500
|
-
self.el.className += ' is-bound';
|
|
7501
|
-
addEvent(opts.trigger, 'click', self._onInputClick);
|
|
7502
|
-
addEvent(opts.trigger, 'focus', self._onInputFocus);
|
|
7503
|
-
addEvent(opts.trigger, 'blur', self._onInputBlur);
|
|
7504
|
-
} else {
|
|
7505
|
-
this.show();
|
|
7506
|
-
}
|
|
7507
|
-
};
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
/**
|
|
7511
|
-
* public Pikaday API
|
|
7512
|
-
*/
|
|
7513
|
-
Pikaday.prototype = {
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
/**
|
|
7517
|
-
* configure functionality
|
|
7518
|
-
*/
|
|
7519
|
-
config: function (options) {
|
|
7520
|
-
if (!this._o) {
|
|
7521
|
-
this._o = extend({}, defaults, true);
|
|
7522
|
-
}
|
|
7523
|
-
|
|
7524
|
-
var opts = extend(this._o, options, true);
|
|
7525
|
-
|
|
7526
|
-
opts.isRTL = !!opts.isRTL;
|
|
7527
|
-
|
|
7528
|
-
opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
|
|
7529
|
-
|
|
7530
|
-
opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
|
|
7531
|
-
|
|
7532
|
-
opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
|
|
7533
|
-
|
|
7534
|
-
opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
|
|
7535
|
-
|
|
7536
|
-
opts.disableWeekends = !!opts.disableWeekends;
|
|
7537
|
-
|
|
7538
|
-
opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
|
|
7539
|
-
|
|
7540
|
-
var nom = parseInt(opts.numberOfMonths, 10) || 1;
|
|
7541
|
-
opts.numberOfMonths = nom > 4 ? 4 : nom;
|
|
7542
|
-
|
|
7543
|
-
if (!isDate(opts.minDate)) {
|
|
7544
|
-
opts.minDate = false;
|
|
7545
|
-
}
|
|
7546
|
-
if (!isDate(opts.maxDate)) {
|
|
7547
|
-
opts.maxDate = false;
|
|
7548
|
-
}
|
|
7549
|
-
if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
|
|
7550
|
-
opts.maxDate = opts.minDate = false;
|
|
7551
|
-
}
|
|
7552
|
-
if (opts.minDate) {
|
|
7553
|
-
this.setMinDate(opts.minDate);
|
|
7554
|
-
}
|
|
7555
|
-
if (opts.maxDate) {
|
|
7556
|
-
this.setMaxDate(opts.maxDate);
|
|
7557
|
-
}
|
|
7558
|
-
|
|
7559
|
-
if (isArray(opts.yearRange)) {
|
|
7560
|
-
var fallback = new Date().getFullYear() - 10;
|
|
7561
|
-
opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
|
|
7562
|
-
opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
|
|
7563
|
-
} else {
|
|
7564
|
-
opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
|
|
7565
|
-
if (opts.yearRange > 100) {
|
|
7566
|
-
opts.yearRange = 100;
|
|
7567
|
-
}
|
|
7568
|
-
}
|
|
7569
|
-
|
|
7570
|
-
return opts;
|
|
7571
|
-
},
|
|
7572
|
-
|
|
7573
|
-
/**
|
|
7574
|
-
* return a formatted string of the current selection (using Moment.js if available)
|
|
7575
|
-
*/
|
|
7576
|
-
toString: function (format) {
|
|
7577
|
-
format = format || this._o.format;
|
|
7578
|
-
if (!isDate(this._d)) {
|
|
7579
|
-
return '';
|
|
7580
|
-
}
|
|
7581
|
-
if (this._o.toString) {
|
|
7582
|
-
return this._o.toString(this._d, format);
|
|
7583
|
-
}
|
|
7584
|
-
if (hasMoment) {
|
|
7585
|
-
return moment(this._d).format(format);
|
|
7586
|
-
}
|
|
7587
|
-
return this._d.toDateString();
|
|
7588
|
-
},
|
|
7589
|
-
|
|
7590
|
-
/**
|
|
7591
|
-
* return a Moment.js object of the current selection (if available)
|
|
7592
|
-
*/
|
|
7593
|
-
getMoment: function () {
|
|
7594
|
-
return hasMoment ? moment(this._d) : null;
|
|
7595
|
-
},
|
|
7596
|
-
|
|
7597
|
-
/**
|
|
7598
|
-
* set the current selection from a Moment.js object (if available)
|
|
7599
|
-
*/
|
|
7600
|
-
setMoment: function (date, preventOnSelect) {
|
|
7601
|
-
if (hasMoment && moment.isMoment(date)) {
|
|
7602
|
-
this.setDate(date.toDate(), preventOnSelect);
|
|
7603
|
-
}
|
|
7604
|
-
},
|
|
7605
|
-
|
|
7606
|
-
/**
|
|
7607
|
-
* return a Date object of the current selection
|
|
7608
|
-
*/
|
|
7609
|
-
getDate: function () {
|
|
7610
|
-
return isDate(this._d) ? new Date(this._d.getTime()) : null;
|
|
7611
|
-
},
|
|
7612
|
-
|
|
7613
|
-
/**
|
|
7614
|
-
* set the current selection
|
|
7615
|
-
*/
|
|
7616
|
-
setDate: function (date, preventOnSelect) {
|
|
7617
|
-
if (!date) {
|
|
7618
|
-
this._d = null;
|
|
7619
|
-
|
|
7620
|
-
if (this._o.field) {
|
|
7621
|
-
this._o.field.value = '';
|
|
7622
|
-
fireEvent(this._o.field, 'change', { firedBy: this });
|
|
7623
|
-
}
|
|
7624
|
-
|
|
7625
|
-
return this.draw();
|
|
7626
|
-
}
|
|
7627
|
-
if (typeof date === 'string') {
|
|
7628
|
-
date = new Date(Date.parse(date));
|
|
7629
|
-
}
|
|
7630
|
-
if (!isDate(date)) {
|
|
7631
|
-
return;
|
|
7632
|
-
}
|
|
7633
|
-
|
|
7634
|
-
var min = this._o.minDate,
|
|
7635
|
-
max = this._o.maxDate;
|
|
7636
|
-
|
|
7637
|
-
if (isDate(min) && date < min) {
|
|
7638
|
-
date = min;
|
|
7639
|
-
} else if (isDate(max) && date > max) {
|
|
7640
|
-
date = max;
|
|
7641
|
-
}
|
|
7642
|
-
|
|
7643
|
-
this._d = new Date(date.getTime());
|
|
7644
|
-
setToStartOfDay(this._d);
|
|
7645
|
-
this.gotoDate(this._d);
|
|
7646
|
-
|
|
7647
|
-
if (this._o.field) {
|
|
7648
|
-
this._o.field.value = this.toString();
|
|
7649
|
-
fireEvent(this._o.field, 'change', { firedBy: this });
|
|
7650
|
-
}
|
|
7651
|
-
if (!preventOnSelect && typeof this._o.onSelect === 'function') {
|
|
7652
|
-
this._o.onSelect.call(this, this.getDate());
|
|
7653
|
-
}
|
|
7654
|
-
},
|
|
7655
|
-
|
|
7656
|
-
/**
|
|
7657
|
-
* change view to a specific date
|
|
7658
|
-
*/
|
|
7659
|
-
gotoDate: function (date) {
|
|
7660
|
-
var newCalendar = true;
|
|
7661
|
-
|
|
7662
|
-
if (!isDate(date)) {
|
|
7663
|
-
return;
|
|
7664
|
-
}
|
|
7665
|
-
|
|
7666
|
-
if (this.calendars) {
|
|
7667
|
-
var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
|
|
7668
|
-
lastVisibleDate = new Date(this.calendars[this.calendars.length - 1].year, this.calendars[this.calendars.length - 1].month, 1),
|
|
7669
|
-
visibleDate = date.getTime();
|
|
7670
|
-
// get the end of the month
|
|
7671
|
-
lastVisibleDate.setMonth(lastVisibleDate.getMonth() + 1);
|
|
7672
|
-
lastVisibleDate.setDate(lastVisibleDate.getDate() - 1);
|
|
7673
|
-
newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
|
|
7674
|
-
}
|
|
7675
|
-
|
|
7676
|
-
if (newCalendar) {
|
|
7677
|
-
this.calendars = [{
|
|
7678
|
-
month: date.getMonth(),
|
|
7679
|
-
year: date.getFullYear()
|
|
7680
|
-
}];
|
|
7681
|
-
if (this._o.mainCalendar === 'right') {
|
|
7682
|
-
this.calendars[0].month += 1 - this._o.numberOfMonths;
|
|
7683
|
-
}
|
|
7684
|
-
}
|
|
7685
|
-
|
|
7686
|
-
this.adjustCalendars();
|
|
7687
|
-
},
|
|
7688
|
-
|
|
7689
|
-
adjustDate: function (sign, days) {
|
|
7690
|
-
|
|
7691
|
-
var day = this.getDate() || new Date();
|
|
7692
|
-
var difference = parseInt(days) * 24 * 60 * 60 * 1000;
|
|
7693
|
-
|
|
7694
|
-
var newDay;
|
|
7695
|
-
|
|
7696
|
-
if (sign === 'add') {
|
|
7697
|
-
newDay = new Date(day.valueOf() + difference);
|
|
7698
|
-
} else if (sign === 'subtract') {
|
|
7699
|
-
newDay = new Date(day.valueOf() - difference);
|
|
7700
|
-
}
|
|
7701
|
-
|
|
7702
|
-
this.setDate(newDay);
|
|
7703
|
-
},
|
|
7704
|
-
|
|
7705
|
-
adjustCalendars: function () {
|
|
7706
|
-
this.calendars[0] = adjustCalendar(this.calendars[0]);
|
|
7707
|
-
for (var c = 1; c < this._o.numberOfMonths; c++) {
|
|
7708
|
-
this.calendars[c] = adjustCalendar({
|
|
7709
|
-
month: this.calendars[0].month + c,
|
|
7710
|
-
year: this.calendars[0].year
|
|
7711
|
-
});
|
|
7712
|
-
}
|
|
7713
|
-
this.draw();
|
|
7714
|
-
},
|
|
7715
|
-
|
|
7716
|
-
gotoToday: function () {
|
|
7717
|
-
this.gotoDate(new Date());
|
|
7718
|
-
},
|
|
7719
|
-
|
|
7720
|
-
/**
|
|
7721
|
-
* change view to a specific month (zero-index, e.g. 0: January)
|
|
7722
|
-
*/
|
|
7723
|
-
gotoMonth: function (month) {
|
|
7724
|
-
if (!isNaN(month)) {
|
|
7725
|
-
this.calendars[0].month = parseInt(month, 10);
|
|
7726
|
-
this.adjustCalendars();
|
|
7727
|
-
}
|
|
7728
|
-
},
|
|
7729
|
-
|
|
7730
|
-
nextMonth: function () {
|
|
7731
|
-
this.calendars[0].month++;
|
|
7732
|
-
this.adjustCalendars();
|
|
7733
|
-
},
|
|
7734
|
-
|
|
7735
|
-
prevMonth: function () {
|
|
7736
|
-
this.calendars[0].month--;
|
|
7737
|
-
this.adjustCalendars();
|
|
7738
|
-
},
|
|
7739
|
-
|
|
7740
|
-
/**
|
|
7741
|
-
* change view to a specific full year (e.g. "2012")
|
|
7742
|
-
*/
|
|
7743
|
-
gotoYear: function (year) {
|
|
7744
|
-
if (!isNaN(year)) {
|
|
7745
|
-
this.calendars[0].year = parseInt(year, 10);
|
|
7746
|
-
this.adjustCalendars();
|
|
7747
|
-
}
|
|
7748
|
-
},
|
|
7749
|
-
|
|
7750
|
-
/**
|
|
7751
|
-
* change the minDate
|
|
7752
|
-
*/
|
|
7753
|
-
setMinDate: function (value) {
|
|
7754
|
-
if (value instanceof Date) {
|
|
7755
|
-
setToStartOfDay(value);
|
|
7756
|
-
this._o.minDate = value;
|
|
7757
|
-
this._o.minYear = value.getFullYear();
|
|
7758
|
-
this._o.minMonth = value.getMonth();
|
|
7759
|
-
} else {
|
|
7760
|
-
this._o.minDate = defaults.minDate;
|
|
7761
|
-
this._o.minYear = defaults.minYear;
|
|
7762
|
-
this._o.minMonth = defaults.minMonth;
|
|
7763
|
-
this._o.startRange = defaults.startRange;
|
|
7764
|
-
}
|
|
7765
|
-
|
|
7766
|
-
this.draw();
|
|
7767
|
-
},
|
|
7768
|
-
|
|
7769
|
-
/**
|
|
7770
|
-
* change the maxDate
|
|
7771
|
-
*/
|
|
7772
|
-
setMaxDate: function (value) {
|
|
7773
|
-
if (value instanceof Date) {
|
|
7774
|
-
setToStartOfDay(value);
|
|
7775
|
-
this._o.maxDate = value;
|
|
7776
|
-
this._o.maxYear = value.getFullYear();
|
|
7777
|
-
this._o.maxMonth = value.getMonth();
|
|
7778
|
-
} else {
|
|
7779
|
-
this._o.maxDate = defaults.maxDate;
|
|
7780
|
-
this._o.maxYear = defaults.maxYear;
|
|
7781
|
-
this._o.maxMonth = defaults.maxMonth;
|
|
7782
|
-
this._o.endRange = defaults.endRange;
|
|
7783
|
-
}
|
|
7784
|
-
|
|
7785
|
-
this.draw();
|
|
7786
|
-
},
|
|
7787
|
-
|
|
7788
|
-
setStartRange: function (value) {
|
|
7789
|
-
this._o.startRange = value;
|
|
7790
|
-
},
|
|
7791
|
-
|
|
7792
|
-
setEndRange: function (value) {
|
|
7793
|
-
this._o.endRange = value;
|
|
7794
|
-
},
|
|
7795
|
-
|
|
7796
|
-
/**
|
|
7797
|
-
* refresh the HTML
|
|
7798
|
-
*/
|
|
7799
|
-
draw: function (force) {
|
|
7800
|
-
if (!this._v && !force) {
|
|
7801
|
-
return;
|
|
7802
|
-
}
|
|
7803
|
-
var opts = this._o,
|
|
7804
|
-
minYear = opts.minYear,
|
|
7805
|
-
maxYear = opts.maxYear,
|
|
7806
|
-
minMonth = opts.minMonth,
|
|
7807
|
-
maxMonth = opts.maxMonth,
|
|
7808
|
-
html = '',
|
|
7809
|
-
randId;
|
|
7810
|
-
|
|
7811
|
-
if (this._y <= minYear) {
|
|
7812
|
-
this._y = minYear;
|
|
7813
|
-
if (!isNaN(minMonth) && this._m < minMonth) {
|
|
7814
|
-
this._m = minMonth;
|
|
7815
|
-
}
|
|
7816
|
-
}
|
|
7817
|
-
if (this._y >= maxYear) {
|
|
7818
|
-
this._y = maxYear;
|
|
7819
|
-
if (!isNaN(maxMonth) && this._m > maxMonth) {
|
|
7820
|
-
this._m = maxMonth;
|
|
7821
|
-
}
|
|
7822
|
-
}
|
|
7823
|
-
|
|
7824
|
-
for (var c = 0; c < opts.numberOfMonths; c++) {
|
|
7825
|
-
randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2);
|
|
7826
|
-
html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '</div>';
|
|
7827
|
-
}
|
|
7828
|
-
|
|
7829
|
-
this.el.innerHTML = html;
|
|
7830
|
-
|
|
7831
|
-
if (opts.bound) {
|
|
7832
|
-
if (opts.field.type !== 'hidden') {
|
|
7833
|
-
sto(function () {
|
|
7834
|
-
opts.trigger.focus();
|
|
7835
|
-
}, 1);
|
|
7836
|
-
}
|
|
7837
|
-
}
|
|
7838
|
-
|
|
7839
|
-
if (typeof this._o.onDraw === 'function') {
|
|
7840
|
-
this._o.onDraw(this);
|
|
7841
|
-
}
|
|
7842
|
-
|
|
7843
|
-
if (opts.bound) {
|
|
7844
|
-
// let the screen reader user know to use arrow keys
|
|
7845
|
-
opts.field.setAttribute('aria-label', 'Use the arrow keys to pick a date');
|
|
7846
|
-
}
|
|
7847
|
-
},
|
|
7848
|
-
|
|
7849
|
-
adjustPosition: function () {
|
|
7850
|
-
var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
|
|
7851
|
-
|
|
7852
|
-
if (this._o.container) return;
|
|
7853
|
-
|
|
7854
|
-
this.el.style.position = 'absolute';
|
|
7855
|
-
|
|
7856
|
-
field = this._o.trigger;
|
|
7857
|
-
pEl = field;
|
|
7858
|
-
width = this.el.offsetWidth;
|
|
7859
|
-
height = this.el.offsetHeight;
|
|
7860
|
-
viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
7861
|
-
viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
7862
|
-
scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
|
|
7863
|
-
|
|
7864
|
-
if (typeof field.getBoundingClientRect === 'function') {
|
|
7865
|
-
clientRect = field.getBoundingClientRect();
|
|
7866
|
-
left = clientRect.left + window.pageXOffset;
|
|
7867
|
-
top = clientRect.bottom + window.pageYOffset;
|
|
7868
|
-
} else {
|
|
7869
|
-
left = pEl.offsetLeft;
|
|
7870
|
-
top = pEl.offsetTop + pEl.offsetHeight;
|
|
7871
|
-
while ((pEl = pEl.offsetParent)) {
|
|
7872
|
-
left += pEl.offsetLeft;
|
|
7873
|
-
top += pEl.offsetTop;
|
|
7874
|
-
}
|
|
7875
|
-
}
|
|
7876
|
-
|
|
7877
|
-
// default position is bottom & left
|
|
7878
|
-
if ((this._o.reposition && left + width > viewportWidth) ||
|
|
7879
|
-
(
|
|
7880
|
-
this._o.position.indexOf('right') > -1 &&
|
|
7881
|
-
left - width + field.offsetWidth > 0
|
|
7882
|
-
)
|
|
7883
|
-
) {
|
|
7884
|
-
left = left - width + field.offsetWidth;
|
|
7885
|
-
}
|
|
7886
|
-
if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
|
|
7887
|
-
(
|
|
7888
|
-
this._o.position.indexOf('top') > -1 &&
|
|
7889
|
-
top - height - field.offsetHeight > 0
|
|
7890
|
-
)
|
|
7891
|
-
) {
|
|
7892
|
-
top = top - height - field.offsetHeight;
|
|
7893
|
-
}
|
|
7894
|
-
|
|
7895
|
-
this.el.style.left = left + 'px';
|
|
7896
|
-
this.el.style.top = top + 'px';
|
|
7897
|
-
},
|
|
7898
|
-
|
|
7899
|
-
/**
|
|
7900
|
-
* render HTML for a particular month
|
|
7901
|
-
*/
|
|
7902
|
-
render: function (year, month, randId) {
|
|
7903
|
-
var opts = this._o,
|
|
7904
|
-
now = new Date(),
|
|
7905
|
-
days = getDaysInMonth(year, month),
|
|
7906
|
-
before = new Date(year, month, 1).getDay(),
|
|
7907
|
-
data = [],
|
|
7908
|
-
row = [];
|
|
7909
|
-
setToStartOfDay(now);
|
|
7910
|
-
if (opts.firstDay > 0) {
|
|
7911
|
-
before -= opts.firstDay;
|
|
7912
|
-
if (before < 0) {
|
|
7913
|
-
before += 7;
|
|
7914
|
-
}
|
|
7915
|
-
}
|
|
7916
|
-
var previousMonth = month === 0 ? 11 : month - 1,
|
|
7917
|
-
nextMonth = month === 11 ? 0 : month + 1,
|
|
7918
|
-
yearOfPreviousMonth = month === 0 ? year - 1 : year,
|
|
7919
|
-
yearOfNextMonth = month === 11 ? year + 1 : year,
|
|
7920
|
-
daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth);
|
|
7921
|
-
var cells = days + before,
|
|
7922
|
-
after = cells;
|
|
7923
|
-
while (after > 7) {
|
|
7924
|
-
after -= 7;
|
|
7925
|
-
}
|
|
7926
|
-
cells += 7 - after;
|
|
7927
|
-
var isWeekSelected = false;
|
|
7928
|
-
for (var i = 0, r = 0; i < cells; i++) {
|
|
7929
|
-
var day = new Date(year, month, 1 + (i - before)),
|
|
7930
|
-
isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
|
|
7931
|
-
isToday = compareDates(day, now),
|
|
7932
|
-
hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false,
|
|
7933
|
-
isEmpty = i < before || i >= (days + before),
|
|
7934
|
-
dayNumber = 1 + (i - before),
|
|
7935
|
-
monthNumber = month,
|
|
7936
|
-
yearNumber = year,
|
|
7937
|
-
isStartRange = opts.startRange && compareDates(opts.startRange, day),
|
|
7938
|
-
isEndRange = opts.endRange && compareDates(opts.endRange, day),
|
|
7939
|
-
isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
|
|
7940
|
-
isDisabled = (opts.minDate && day < opts.minDate) ||
|
|
7941
|
-
(opts.maxDate && day > opts.maxDate) ||
|
|
7942
|
-
(opts.disableWeekends && isWeekend(day)) ||
|
|
7943
|
-
(opts.disableDayFn && opts.disableDayFn(day));
|
|
7944
|
-
|
|
7945
|
-
if (isEmpty) {
|
|
7946
|
-
if (i < before) {
|
|
7947
|
-
dayNumber = daysInPreviousMonth + dayNumber;
|
|
7948
|
-
monthNumber = previousMonth;
|
|
7949
|
-
yearNumber = yearOfPreviousMonth;
|
|
7950
|
-
} else {
|
|
7951
|
-
dayNumber = dayNumber - days;
|
|
7952
|
-
monthNumber = nextMonth;
|
|
7953
|
-
yearNumber = yearOfNextMonth;
|
|
7954
|
-
}
|
|
7955
|
-
}
|
|
7956
|
-
|
|
7957
|
-
var dayConfig = {
|
|
7958
|
-
day: dayNumber,
|
|
7959
|
-
month: monthNumber,
|
|
7960
|
-
year: yearNumber,
|
|
7961
|
-
hasEvent: hasEvent,
|
|
7962
|
-
isSelected: isSelected,
|
|
7963
|
-
isToday: isToday,
|
|
7964
|
-
isDisabled: isDisabled,
|
|
7965
|
-
isEmpty: isEmpty,
|
|
7966
|
-
isStartRange: isStartRange,
|
|
7967
|
-
isEndRange: isEndRange,
|
|
7968
|
-
isInRange: isInRange,
|
|
7969
|
-
showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths,
|
|
7970
|
-
enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths
|
|
7971
|
-
};
|
|
7972
|
-
|
|
7973
|
-
if (opts.pickWholeWeek && isSelected) {
|
|
7974
|
-
isWeekSelected = true;
|
|
7975
|
-
}
|
|
7976
|
-
|
|
7977
|
-
row.push(renderDay(dayConfig));
|
|
7978
|
-
|
|
7979
|
-
if (++r === 7) {
|
|
7980
|
-
if (opts.showWeekNumber) {
|
|
7981
|
-
row.unshift(renderWeek(i - before, month, year));
|
|
7982
|
-
}
|
|
7983
|
-
data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected));
|
|
7984
|
-
row = [];
|
|
7985
|
-
r = 0;
|
|
7986
|
-
isWeekSelected = false;
|
|
7987
|
-
}
|
|
7988
|
-
}
|
|
7989
|
-
return renderTable(opts, data, randId);
|
|
7990
|
-
},
|
|
7991
|
-
|
|
7992
|
-
isVisible: function () {
|
|
7993
|
-
return this._v;
|
|
7994
|
-
},
|
|
7995
|
-
|
|
7996
|
-
show: function () {
|
|
7997
|
-
if (!this.isVisible()) {
|
|
7998
|
-
this._v = true;
|
|
7999
|
-
this.draw();
|
|
8000
|
-
removeClass(this.el, 'is-hidden');
|
|
8001
|
-
if (this._o.bound) {
|
|
8002
|
-
addEvent(document, 'click', this._onClick);
|
|
8003
|
-
this.adjustPosition();
|
|
8004
|
-
}
|
|
8005
|
-
if (typeof this._o.onOpen === 'function') {
|
|
8006
|
-
this._o.onOpen.call(this);
|
|
8007
|
-
}
|
|
8008
|
-
}
|
|
8009
|
-
},
|
|
8010
|
-
|
|
8011
|
-
hide: function () {
|
|
8012
|
-
var v = this._v;
|
|
8013
|
-
if (v !== false) {
|
|
8014
|
-
if (this._o.bound) {
|
|
8015
|
-
removeEvent(document, 'click', this._onClick);
|
|
8016
|
-
}
|
|
8017
|
-
this.el.style.position = 'static'; // reset
|
|
8018
|
-
this.el.style.left = 'auto';
|
|
8019
|
-
this.el.style.top = 'auto';
|
|
8020
|
-
addClass(this.el, 'is-hidden');
|
|
8021
|
-
this._v = false;
|
|
8022
|
-
if (v !== undefined && typeof this._o.onClose === 'function') {
|
|
8023
|
-
this._o.onClose.call(this);
|
|
8024
|
-
}
|
|
8025
|
-
}
|
|
8026
|
-
},
|
|
8027
|
-
|
|
8028
|
-
/**
|
|
8029
|
-
* GAME OVER
|
|
8030
|
-
*/
|
|
8031
|
-
destroy: function () {
|
|
8032
|
-
var opts = this._o;
|
|
8033
|
-
|
|
8034
|
-
this.hide();
|
|
8035
|
-
removeEvent(this.el, 'mousedown', this._onMouseDown, true);
|
|
8036
|
-
removeEvent(this.el, 'touchend', this._onMouseDown, true);
|
|
8037
|
-
removeEvent(this.el, 'change', this._onChange);
|
|
8038
|
-
if (opts.keyboardInput) {
|
|
8039
|
-
removeEvent(document, 'keydown', this._onKeyChange);
|
|
8040
|
-
}
|
|
8041
|
-
if (opts.field) {
|
|
8042
|
-
removeEvent(opts.field, 'change', this._onInputChange);
|
|
8043
|
-
if (opts.bound) {
|
|
8044
|
-
removeEvent(opts.trigger, 'click', this._onInputClick);
|
|
8045
|
-
removeEvent(opts.trigger, 'focus', this._onInputFocus);
|
|
8046
|
-
removeEvent(opts.trigger, 'blur', this._onInputBlur);
|
|
8047
|
-
}
|
|
8048
|
-
}
|
|
8049
|
-
if (this.el.parentNode) {
|
|
8050
|
-
this.el.parentNode.removeChild(this.el);
|
|
8051
|
-
}
|
|
8052
|
-
}
|
|
8053
|
-
|
|
8054
|
-
};
|
|
8055
|
-
|
|
8056
|
-
return Pikaday;
|
|
8057
|
-
}));
|
|
8058
|
-
} (pikaday$1));
|
|
8059
|
-
return pikaday$1.exports;
|
|
8060
|
-
}
|
|
8061
|
-
|
|
8062
|
-
var pikadayExports = /*@__PURE__*/ requirePikaday();
|
|
8063
|
-
var Pikaday = /*@__PURE__*/getDefaultExportFromCjs(pikadayExports);
|
|
8064
|
-
|
|
8065
6911
|
// Ensure moment is available globally for Pikaday
|
|
8066
6912
|
if (typeof window !== 'undefined') {
|
|
8067
6913
|
window.moment = moment$1;
|
|
@@ -8656,7 +7502,30 @@ class DateTimeButton extends ChartComponent {
|
|
|
8656
7502
|
this.pickerIsVisible = false;
|
|
8657
7503
|
}
|
|
8658
7504
|
buttonDateTimeFormat(millis) {
|
|
8659
|
-
|
|
7505
|
+
const date = new Date(millis);
|
|
7506
|
+
const locale = this.chartOptions.dateLocale || 'en-US';
|
|
7507
|
+
const is24Hour = this.chartOptions.is24HourTime !== false;
|
|
7508
|
+
const formatOptions = {
|
|
7509
|
+
year: 'numeric',
|
|
7510
|
+
month: '2-digit',
|
|
7511
|
+
day: '2-digit',
|
|
7512
|
+
hour: '2-digit',
|
|
7513
|
+
minute: '2-digit',
|
|
7514
|
+
second: '2-digit',
|
|
7515
|
+
hour12: !is24Hour
|
|
7516
|
+
};
|
|
7517
|
+
try {
|
|
7518
|
+
if (this.chartOptions.offset && this.chartOptions.offset !== 'Local') {
|
|
7519
|
+
formatOptions.timeZone = this.getTimezoneFromOffset(this.chartOptions.offset);
|
|
7520
|
+
}
|
|
7521
|
+
const baseFormat = date.toLocaleString(locale, formatOptions);
|
|
7522
|
+
const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
|
|
7523
|
+
return `${baseFormat}.${milliseconds}`;
|
|
7524
|
+
}
|
|
7525
|
+
catch (error) {
|
|
7526
|
+
console.warn(`Failed to format date for locale ${locale}:`, error);
|
|
7527
|
+
return Utils.timeFormat(!this.chartOptions.minutesForTimeLabels, !this.chartOptions.minutesForTimeLabels, this.chartOptions.offset, this.chartOptions.is24HourTime, 0, null, this.chartOptions.dateLocale)(millis);
|
|
7528
|
+
}
|
|
8660
7529
|
}
|
|
8661
7530
|
render(chartOptions, minMillis, maxMillis, onSet = null) {
|
|
8662
7531
|
this.chartOptions.setOptions(chartOptions);
|
|
@@ -8676,11 +7545,22 @@ class DateTimeButton extends ChartComponent {
|
|
|
8676
7545
|
}
|
|
8677
7546
|
super.themify(d3__namespace.select(this.renderTarget), this.chartOptions.theme);
|
|
8678
7547
|
}
|
|
7548
|
+
getTimezoneFromOffset(offset) {
|
|
7549
|
+
const timezoneMap = {
|
|
7550
|
+
'UTC': 'UTC',
|
|
7551
|
+
'EST': 'America/New_York',
|
|
7552
|
+
'PST': 'America/Los_Angeles',
|
|
7553
|
+
'CST': 'America/Chicago',
|
|
7554
|
+
'MST': 'America/Denver'
|
|
7555
|
+
};
|
|
7556
|
+
return timezoneMap[offset] || 'UTC';
|
|
7557
|
+
}
|
|
8679
7558
|
}
|
|
8680
7559
|
|
|
8681
7560
|
class DateTimeButtonRange extends DateTimeButton {
|
|
8682
7561
|
constructor(renderTarget) {
|
|
8683
7562
|
super(renderTarget);
|
|
7563
|
+
this.clickOutsideHandler = null;
|
|
8684
7564
|
}
|
|
8685
7565
|
setButtonText(fromMillis, toMillis, isRelative, quickTime) {
|
|
8686
7566
|
let fromString = this.buttonDateTimeFormat(fromMillis);
|
|
@@ -8700,10 +7580,38 @@ class DateTimeButtonRange extends DateTimeButton {
|
|
|
8700
7580
|
onClose() {
|
|
8701
7581
|
this.dateTimePickerContainer.style("display", "none");
|
|
8702
7582
|
this.dateTimeButton.node().focus();
|
|
7583
|
+
this.removeClickOutsideHandler();
|
|
7584
|
+
}
|
|
7585
|
+
removeClickOutsideHandler() {
|
|
7586
|
+
if (this.clickOutsideHandler) {
|
|
7587
|
+
document.removeEventListener('click', this.clickOutsideHandler);
|
|
7588
|
+
this.clickOutsideHandler = null;
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
setupClickOutsideHandler() {
|
|
7592
|
+
// Remove any existing handler first
|
|
7593
|
+
this.removeClickOutsideHandler();
|
|
7594
|
+
// Add handler after a small delay to prevent the opening click from immediately closing the picker
|
|
7595
|
+
setTimeout(() => {
|
|
7596
|
+
this.clickOutsideHandler = (event) => {
|
|
7597
|
+
const pickerElement = this.dateTimePickerContainer.node();
|
|
7598
|
+
const buttonElement = this.dateTimeButton.node();
|
|
7599
|
+
const target = event.target;
|
|
7600
|
+
// Check if click is outside both the picker and the button
|
|
7601
|
+
if (pickerElement && buttonElement &&
|
|
7602
|
+
!pickerElement.contains(target) &&
|
|
7603
|
+
!buttonElement.contains(target)) {
|
|
7604
|
+
this.onClose();
|
|
7605
|
+
}
|
|
7606
|
+
};
|
|
7607
|
+
document.addEventListener('click', this.clickOutsideHandler);
|
|
7608
|
+
}, 0);
|
|
8703
7609
|
}
|
|
8704
7610
|
render(chartOptions = {}, minMillis, maxMillis, fromMillis = null, toMillis = null, onSet = null, onCancel = null) {
|
|
8705
7611
|
super.render(chartOptions, minMillis, maxMillis, onSet);
|
|
8706
|
-
d3__namespace.select(this.renderTarget)
|
|
7612
|
+
let container = d3__namespace.select(this.renderTarget);
|
|
7613
|
+
container.classed('tsi-dateTimeContainerRange', true);
|
|
7614
|
+
container.style('position', 'relative');
|
|
8707
7615
|
this.fromMillis = fromMillis;
|
|
8708
7616
|
this.toMillis = toMillis;
|
|
8709
7617
|
this.onCancel = onCancel ? onCancel : () => { };
|
|
@@ -8729,6 +7637,7 @@ class DateTimeButtonRange extends DateTimeButton {
|
|
|
8729
7637
|
this.onClose();
|
|
8730
7638
|
this.onCancel();
|
|
8731
7639
|
});
|
|
7640
|
+
this.setupClickOutsideHandler();
|
|
8732
7641
|
}
|
|
8733
7642
|
});
|
|
8734
7643
|
}
|
|
@@ -12443,7 +11352,7 @@ class ModelAutocomplete extends Component {
|
|
|
12443
11352
|
super(renderTarget);
|
|
12444
11353
|
this.chartOptions = new ChartOptions(); // TODO handle onkeyup and oninput in chart options
|
|
12445
11354
|
}
|
|
12446
|
-
render(
|
|
11355
|
+
render(chartOptions) {
|
|
12447
11356
|
this.chartOptions.setOptions(chartOptions);
|
|
12448
11357
|
let targetElement = d3__namespace.select(this.renderTarget);
|
|
12449
11358
|
targetElement.html("");
|
|
@@ -12797,20 +11706,102 @@ class TsqExpression extends ChartDataOptions {
|
|
|
12797
11706
|
}
|
|
12798
11707
|
}
|
|
12799
11708
|
|
|
11709
|
+
// Centralized renderer for the hierarchy tree. Keeps a stable D3 data-join and
|
|
11710
|
+
// updates existing DOM nodes instead of fully recreating them on each render.
|
|
11711
|
+
class TreeRenderer {
|
|
11712
|
+
static render(owner, data, target) {
|
|
11713
|
+
// Ensure an <ul> exists for this target (one list per level)
|
|
11714
|
+
let list = target.select('ul');
|
|
11715
|
+
if (list.empty()) {
|
|
11716
|
+
list = target.append('ul').attr('role', target === owner.hierarchyElem ? 'tree' : 'group');
|
|
11717
|
+
}
|
|
11718
|
+
const entries = Object.keys(data).map(k => ({ key: k, item: data[k] }));
|
|
11719
|
+
const liSelection = list.selectAll('li').data(entries, (d) => d && d.key);
|
|
11720
|
+
liSelection.exit().remove();
|
|
11721
|
+
const liEnter = liSelection.enter().append('li')
|
|
11722
|
+
.attr('role', 'none')
|
|
11723
|
+
.classed('tsi-leaf', (d) => !!d.item.isLeaf);
|
|
11724
|
+
const liMerged = liEnter.merge(liSelection);
|
|
11725
|
+
const setSize = entries.length;
|
|
11726
|
+
liMerged.each((d, i, nodes) => {
|
|
11727
|
+
const entry = d;
|
|
11728
|
+
const li = d3__namespace.select(nodes[i]);
|
|
11729
|
+
if (owner.selectedIds && owner.selectedIds.includes(entry.item.id)) {
|
|
11730
|
+
li.classed('tsi-selected', true);
|
|
11731
|
+
}
|
|
11732
|
+
else {
|
|
11733
|
+
li.classed('tsi-selected', false);
|
|
11734
|
+
}
|
|
11735
|
+
// determine instance vs hierarchy node by presence of isLeaf flag
|
|
11736
|
+
const isInstance = !!entry.item.isLeaf;
|
|
11737
|
+
const nodeNameToCheckIfExists = isInstance ? owner.instanceNodeString(entry.item) : entry.key;
|
|
11738
|
+
const displayName = (entry.item && (entry.item.displayName || nodeNameToCheckIfExists)) || nodeNameToCheckIfExists;
|
|
11739
|
+
li.attr('data-display-name', displayName);
|
|
11740
|
+
let itemElem = li.select('.tsi-hierarchyItem');
|
|
11741
|
+
if (itemElem.empty()) {
|
|
11742
|
+
const newListElem = owner.createHierarchyItemElem(entry.item, entry.key);
|
|
11743
|
+
li.node().appendChild(newListElem.node());
|
|
11744
|
+
itemElem = li.select('.tsi-hierarchyItem');
|
|
11745
|
+
}
|
|
11746
|
+
itemElem.attr('aria-label', isInstance ? owner.getAriaLabel(entry.item) : entry.key);
|
|
11747
|
+
itemElem.attr('title', isInstance ? owner.getAriaLabel(entry.item) : entry.key);
|
|
11748
|
+
itemElem.attr('aria-expanded', String(entry.item.isExpanded));
|
|
11749
|
+
// accessibility: set treeitem level and position in set
|
|
11750
|
+
const ariaLevel = String(((entry.item && typeof entry.item.level === 'number') ? entry.item.level : 0) + 1);
|
|
11751
|
+
itemElem.attr('aria-level', ariaLevel);
|
|
11752
|
+
itemElem.attr('aria-posinset', String(i + 1));
|
|
11753
|
+
itemElem.attr('aria-setsize', String(setSize));
|
|
11754
|
+
if (!isInstance) {
|
|
11755
|
+
itemElem.select('.tsi-caret-icon').attr('style', `left: ${(entry.item.level) * 18 + 20}px`);
|
|
11756
|
+
itemElem.select('.tsi-name').text(entry.key);
|
|
11757
|
+
itemElem.select('.tsi-instanceCount').text(entry.item.cumulativeInstanceCount);
|
|
11758
|
+
}
|
|
11759
|
+
else {
|
|
11760
|
+
const nameSpan = itemElem.select('.tsi-name');
|
|
11761
|
+
nameSpan.html('');
|
|
11762
|
+
Utils.appendFormattedElementsFromString(nameSpan, owner.instanceNodeStringToDisplay(entry.item));
|
|
11763
|
+
}
|
|
11764
|
+
entry.item.node = li;
|
|
11765
|
+
if (entry.item.children) {
|
|
11766
|
+
entry.item.isExpanded = true;
|
|
11767
|
+
li.classed('tsi-expanded', true);
|
|
11768
|
+
// recurse using TreeRenderer to keep rendering logic centralized
|
|
11769
|
+
TreeRenderer.render(owner, entry.item.children, li);
|
|
11770
|
+
}
|
|
11771
|
+
else {
|
|
11772
|
+
li.classed('tsi-expanded', false);
|
|
11773
|
+
li.selectAll('ul').remove();
|
|
11774
|
+
}
|
|
11775
|
+
});
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
|
|
12800
11779
|
class HierarchyNavigation extends Component {
|
|
12801
11780
|
constructor(renderTarget) {
|
|
12802
11781
|
super(renderTarget);
|
|
12803
11782
|
this.path = [];
|
|
11783
|
+
// debounce + request cancellation fields
|
|
11784
|
+
this.debounceTimer = null;
|
|
11785
|
+
this.debounceDelay = 250; // ms
|
|
11786
|
+
this.requestCounter = 0; // increments for each outgoing request
|
|
11787
|
+
this.latestRequestId = 0; // id of the most recent request
|
|
12804
11788
|
//selectedIds
|
|
12805
11789
|
this.selectedIds = [];
|
|
12806
11790
|
this.searchEnabled = true;
|
|
12807
|
-
this.
|
|
11791
|
+
this.autocompleteEnabled = true; // Enable/disable autocomplete suggestions
|
|
11792
|
+
// Search mode state
|
|
11793
|
+
this.isSearchMode = false;
|
|
11794
|
+
// Paths that should be auto-expanded (Set of path strings like "Factory North/Building A")
|
|
11795
|
+
this.pathsToAutoExpand = new Set();
|
|
11796
|
+
this.renderSearchResult = async (r, payload, target) => {
|
|
12808
11797
|
const hierarchyData = r.hierarchyNodes?.hits?.length
|
|
12809
11798
|
? this.fillDataRecursively(r.hierarchyNodes, payload, payload)
|
|
12810
11799
|
: {};
|
|
12811
11800
|
const instancesData = r.instances?.hits?.length
|
|
12812
11801
|
? r.instances.hits.reduce((acc, i) => {
|
|
12813
|
-
|
|
11802
|
+
const inst = new InstanceNode(i.timeSeriesId, i.name, payload.path.length - this.path.length, i.id, i.description);
|
|
11803
|
+
inst.displayName = this.instanceNodeStringToDisplay(i) || '';
|
|
11804
|
+
acc[this.instanceNodeIdentifier(i)] = inst;
|
|
12814
11805
|
return acc;
|
|
12815
11806
|
}, {})
|
|
12816
11807
|
: {};
|
|
@@ -12821,7 +11812,17 @@ class HierarchyNavigation extends Component {
|
|
|
12821
11812
|
}
|
|
12822
11813
|
hitCountElem.text(r.hierarchyNodes.hitCount);
|
|
12823
11814
|
}
|
|
12824
|
-
|
|
11815
|
+
const merged = { ...hierarchyData, ...instancesData };
|
|
11816
|
+
this.renderTree(merged, target);
|
|
11817
|
+
// Auto-expand nodes that should be expanded and load their children
|
|
11818
|
+
for (const key in hierarchyData) {
|
|
11819
|
+
const node = hierarchyData[key];
|
|
11820
|
+
if (node.isExpanded && !node.children) {
|
|
11821
|
+
// This node should be expanded but doesn't have children loaded yet
|
|
11822
|
+
// We need to trigger expansion after the node is rendered
|
|
11823
|
+
await this.autoExpandNode(node);
|
|
11824
|
+
}
|
|
11825
|
+
}
|
|
12825
11826
|
};
|
|
12826
11827
|
this.hierarchyNodeIdentifier = (hName) => {
|
|
12827
11828
|
return hName ? hName : '(' + this.getString("Empty") + ')';
|
|
@@ -12843,12 +11844,20 @@ class HierarchyNavigation extends Component {
|
|
|
12843
11844
|
const targetElement = d3__namespace.select(this.renderTarget).text('');
|
|
12844
11845
|
this.hierarchyNavWrapper = this.createHierarchyNavWrapper(targetElement);
|
|
12845
11846
|
this.selectedIds = preselectedIds;
|
|
11847
|
+
// Allow disabling autocomplete via options
|
|
11848
|
+
if (hierarchyNavOptions.autocompleteEnabled !== undefined) {
|
|
11849
|
+
this.autocompleteEnabled = hierarchyNavOptions.autocompleteEnabled;
|
|
11850
|
+
}
|
|
11851
|
+
// Pre-compute paths that need to be auto-expanded for preselected instances
|
|
11852
|
+
if (preselectedIds && preselectedIds.length > 0) {
|
|
11853
|
+
await this.computePathsToAutoExpand(preselectedIds);
|
|
11854
|
+
}
|
|
12846
11855
|
//render search wrapper
|
|
12847
|
-
|
|
11856
|
+
this.renderSearchBox();
|
|
12848
11857
|
super.themify(this.hierarchyNavWrapper, this.chartOptions.theme);
|
|
12849
11858
|
const results = this.createResultsWrapper(this.hierarchyNavWrapper);
|
|
12850
11859
|
this.hierarchyElem = this.createHierarchyElem(results);
|
|
12851
|
-
this.pathSearchAndRenderResult({ search: { payload: this.requestPayload() }, render: { target: this.hierarchyElem } });
|
|
11860
|
+
await this.pathSearchAndRenderResult({ search: { payload: this.requestPayload() }, render: { target: this.hierarchyElem } });
|
|
12852
11861
|
}
|
|
12853
11862
|
createHierarchyNavWrapper(targetElement) {
|
|
12854
11863
|
return targetElement.append('div').attr('class', 'tsi-hierarchy-nav-wrapper');
|
|
@@ -12856,8 +11865,129 @@ class HierarchyNavigation extends Component {
|
|
|
12856
11865
|
createResultsWrapper(hierarchyNavWrapper) {
|
|
12857
11866
|
return hierarchyNavWrapper.append('div').classed('tsi-hierarchy-or-list-wrapper', true);
|
|
12858
11867
|
}
|
|
11868
|
+
// create hierarchy container and attach keyboard handler
|
|
12859
11869
|
createHierarchyElem(results) {
|
|
12860
|
-
|
|
11870
|
+
const sel = results.append('div').classed('tsi-hierarchy', true).attr("role", "navigation").on('scroll', () => { });
|
|
11871
|
+
// attach keydown listener for keyboard navigation (delegated)
|
|
11872
|
+
// use native event to preserve focus handling
|
|
11873
|
+
const node = sel.node();
|
|
11874
|
+
if (node) {
|
|
11875
|
+
node.addEventListener('keydown', (ev) => this.onKeyDown(ev));
|
|
11876
|
+
}
|
|
11877
|
+
return sel;
|
|
11878
|
+
}
|
|
11879
|
+
// Keyboard navigation handlers and helpers
|
|
11880
|
+
onKeyDown(ev) {
|
|
11881
|
+
const key = ev.key;
|
|
11882
|
+
const active = document.activeElement;
|
|
11883
|
+
const container = this.hierarchyElem?.node();
|
|
11884
|
+
if (!container)
|
|
11885
|
+
return;
|
|
11886
|
+
const isInside = active && container.contains(active);
|
|
11887
|
+
if (!isInside && (key === 'ArrowDown' || key === 'ArrowUp')) {
|
|
11888
|
+
// focus first visible item on navigation keys
|
|
11889
|
+
const visible = this.getVisibleItemElems();
|
|
11890
|
+
if (visible.length) {
|
|
11891
|
+
this.focusItem(visible[0]);
|
|
11892
|
+
ev.preventDefault();
|
|
11893
|
+
}
|
|
11894
|
+
return;
|
|
11895
|
+
}
|
|
11896
|
+
if (!active)
|
|
11897
|
+
return;
|
|
11898
|
+
const current = active.classList && active.classList.contains('tsi-hierarchyItem') ? active : active.closest('.tsi-hierarchyItem');
|
|
11899
|
+
if (!current)
|
|
11900
|
+
return;
|
|
11901
|
+
switch (key) {
|
|
11902
|
+
case 'ArrowDown':
|
|
11903
|
+
this.focusNext(current);
|
|
11904
|
+
ev.preventDefault();
|
|
11905
|
+
break;
|
|
11906
|
+
case 'ArrowUp':
|
|
11907
|
+
this.focusPrev(current);
|
|
11908
|
+
ev.preventDefault();
|
|
11909
|
+
break;
|
|
11910
|
+
case 'ArrowRight':
|
|
11911
|
+
this.handleArrowRight(current);
|
|
11912
|
+
ev.preventDefault();
|
|
11913
|
+
break;
|
|
11914
|
+
case 'ArrowLeft':
|
|
11915
|
+
this.handleArrowLeft(current);
|
|
11916
|
+
ev.preventDefault();
|
|
11917
|
+
break;
|
|
11918
|
+
case 'Enter':
|
|
11919
|
+
case ' ':
|
|
11920
|
+
// activate (toggle expand or select)
|
|
11921
|
+
current.click();
|
|
11922
|
+
ev.preventDefault();
|
|
11923
|
+
break;
|
|
11924
|
+
}
|
|
11925
|
+
}
|
|
11926
|
+
getVisibleItemElems() {
|
|
11927
|
+
if (!this.hierarchyElem)
|
|
11928
|
+
return [];
|
|
11929
|
+
const root = this.hierarchyElem.node();
|
|
11930
|
+
if (!root)
|
|
11931
|
+
return [];
|
|
11932
|
+
const items = Array.from(root.querySelectorAll('.tsi-hierarchyItem'));
|
|
11933
|
+
return items.filter(i => i.offsetParent !== null && getComputedStyle(i).display !== 'none');
|
|
11934
|
+
}
|
|
11935
|
+
focusItem(elem) {
|
|
11936
|
+
if (!this.hierarchyElem)
|
|
11937
|
+
return;
|
|
11938
|
+
const root = this.hierarchyElem.node();
|
|
11939
|
+
if (!root)
|
|
11940
|
+
return;
|
|
11941
|
+
const items = Array.from(root.querySelectorAll('.tsi-hierarchyItem'));
|
|
11942
|
+
items.forEach(i => i.setAttribute('tabindex', '-1'));
|
|
11943
|
+
elem.setAttribute('tabindex', '0');
|
|
11944
|
+
elem.focus();
|
|
11945
|
+
}
|
|
11946
|
+
focusNext(current) {
|
|
11947
|
+
const visible = this.getVisibleItemElems();
|
|
11948
|
+
const idx = visible.indexOf(current);
|
|
11949
|
+
if (idx >= 0 && idx < visible.length - 1) {
|
|
11950
|
+
this.focusItem(visible[idx + 1]);
|
|
11951
|
+
}
|
|
11952
|
+
}
|
|
11953
|
+
focusPrev(current) {
|
|
11954
|
+
const visible = this.getVisibleItemElems();
|
|
11955
|
+
const idx = visible.indexOf(current);
|
|
11956
|
+
if (idx > 0) {
|
|
11957
|
+
this.focusItem(visible[idx - 1]);
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
handleArrowRight(current) {
|
|
11961
|
+
const caret = current.querySelector('.tsi-caret-icon');
|
|
11962
|
+
const expanded = current.getAttribute('aria-expanded') === 'true';
|
|
11963
|
+
if (caret && !expanded) {
|
|
11964
|
+
// expand
|
|
11965
|
+
current.click();
|
|
11966
|
+
return;
|
|
11967
|
+
}
|
|
11968
|
+
// if already expanded, move to first child
|
|
11969
|
+
if (caret && expanded) {
|
|
11970
|
+
const li = current.closest('li');
|
|
11971
|
+
const childLi = li?.querySelector('ul > li');
|
|
11972
|
+
const childItem = childLi?.querySelector('.tsi-hierarchyItem');
|
|
11973
|
+
if (childItem)
|
|
11974
|
+
this.focusItem(childItem);
|
|
11975
|
+
}
|
|
11976
|
+
}
|
|
11977
|
+
handleArrowLeft(current) {
|
|
11978
|
+
const caret = current.querySelector('.tsi-caret-icon');
|
|
11979
|
+
const expanded = current.getAttribute('aria-expanded') === 'true';
|
|
11980
|
+
if (caret && expanded) {
|
|
11981
|
+
// collapse
|
|
11982
|
+
current.click();
|
|
11983
|
+
return;
|
|
11984
|
+
}
|
|
11985
|
+
// move focus to parent
|
|
11986
|
+
const li = current.closest('li');
|
|
11987
|
+
const parentLi = li?.parentElement?.closest('li');
|
|
11988
|
+
const parentItem = parentLi?.querySelector('.tsi-hierarchyItem');
|
|
11989
|
+
if (parentItem)
|
|
11990
|
+
this.focusItem(parentItem);
|
|
12861
11991
|
}
|
|
12862
11992
|
// prepares the parameters for search request
|
|
12863
11993
|
requestPayload(hierarchy = null) {
|
|
@@ -12866,32 +11996,7 @@ class HierarchyNavigation extends Component {
|
|
|
12866
11996
|
}
|
|
12867
11997
|
// renders tree for both 'Navigate' and 'Filter' mode (with Hierarchy View option selected), locInTarget refers to the 'show more' element -either hierarchy or instance- within the target
|
|
12868
11998
|
renderTree(data, target) {
|
|
12869
|
-
|
|
12870
|
-
Object.keys(data).forEach(el => {
|
|
12871
|
-
let nodeNameToCheckIfExists = data[el] instanceof InstanceNode ? this.instanceNodeString(data[el]) : el;
|
|
12872
|
-
let li;
|
|
12873
|
-
if (list.selectAll(".tsi-name").nodes().find(e => e.innerText === nodeNameToCheckIfExists)) {
|
|
12874
|
-
li = null;
|
|
12875
|
-
}
|
|
12876
|
-
else {
|
|
12877
|
-
li = list.append('li').classed('tsi-leaf', data[el].isLeaf);
|
|
12878
|
-
//if the node is already selected, we want to highlight it
|
|
12879
|
-
if (this.selectedIds && this.selectedIds.includes(data[el].id)) {
|
|
12880
|
-
li.classed('tsi-selected', true);
|
|
12881
|
-
}
|
|
12882
|
-
}
|
|
12883
|
-
if (!li)
|
|
12884
|
-
return;
|
|
12885
|
-
li.attr("role", "none");
|
|
12886
|
-
let newListElem = this.createHierarchyItemElem(data[el], el);
|
|
12887
|
-
li.node().appendChild(newListElem.node());
|
|
12888
|
-
data[el].node = li;
|
|
12889
|
-
if (data[el].children) {
|
|
12890
|
-
data[el].isExpanded = true;
|
|
12891
|
-
data[el].node.classed('tsi-expanded', true);
|
|
12892
|
-
this.renderTree(data[el].children, data[el].node);
|
|
12893
|
-
}
|
|
12894
|
-
});
|
|
11999
|
+
TreeRenderer.render(this, data, target);
|
|
12895
12000
|
}
|
|
12896
12001
|
renderSearchBox() {
|
|
12897
12002
|
this.searchWrapperElem = this.hierarchyNavWrapper.append('div').classed('tsi-hierarchy-search', true);
|
|
@@ -12900,40 +12005,140 @@ class HierarchyNavigation extends Component {
|
|
|
12900
12005
|
let input = inputWrapper
|
|
12901
12006
|
.append("input")
|
|
12902
12007
|
.attr("class", "tsi-searchInput")
|
|
12903
|
-
.attr("aria-label", this.getString("Search
|
|
12904
|
-
.attr("aria-describedby", "tsi-search-desc")
|
|
12008
|
+
.attr("aria-label", this.getString("Search"))
|
|
12009
|
+
.attr("aria-describedby", "tsi-hierarchy-search-desc")
|
|
12905
12010
|
.attr("role", "combobox")
|
|
12906
12011
|
.attr("aria-owns", "tsi-search-results")
|
|
12907
12012
|
.attr("aria-expanded", "false")
|
|
12908
12013
|
.attr("aria-haspopup", "listbox")
|
|
12909
|
-
.attr("placeholder", this.getString("Search
|
|
12014
|
+
.attr("placeholder", this.getString("Search") + "...");
|
|
12015
|
+
// Add ARIA description for screen readers
|
|
12016
|
+
inputWrapper
|
|
12017
|
+
.append("span")
|
|
12018
|
+
.attr("id", "tsi-hierarchy-search-desc")
|
|
12019
|
+
.style("display", "none")
|
|
12020
|
+
.text(this.getString("Search suggestion instruction") || "Use arrow keys to navigate suggestions");
|
|
12021
|
+
// Add live region for search results info
|
|
12022
|
+
inputWrapper
|
|
12023
|
+
.append("div")
|
|
12024
|
+
.attr("class", "tsi-search-results-info")
|
|
12025
|
+
.attr("aria-live", "assertive");
|
|
12026
|
+
// Add clear button
|
|
12027
|
+
let clear = inputWrapper
|
|
12028
|
+
.append("div")
|
|
12029
|
+
.attr("class", "tsi-clear")
|
|
12030
|
+
.attr("tabindex", "0")
|
|
12031
|
+
.attr("role", "button")
|
|
12032
|
+
.attr("aria-label", "Clear Search")
|
|
12033
|
+
.on("click keydown", function (event) {
|
|
12034
|
+
if (Utils.isKeyDownAndNotEnter(event)) {
|
|
12035
|
+
return;
|
|
12036
|
+
}
|
|
12037
|
+
input.node().value = "";
|
|
12038
|
+
self.exitSearchMode();
|
|
12039
|
+
self.ap.close();
|
|
12040
|
+
d3__namespace.select(this).classed("tsi-shown", false);
|
|
12041
|
+
});
|
|
12042
|
+
// Initialize Awesomplete for autocomplete (only if enabled)
|
|
12043
|
+
let Awesomplete = window.Awesomplete;
|
|
12044
|
+
if (this.autocompleteEnabled && Awesomplete) {
|
|
12045
|
+
this.ap = new Awesomplete(input.node(), {
|
|
12046
|
+
minChars: 1,
|
|
12047
|
+
maxItems: 10,
|
|
12048
|
+
autoFirst: true
|
|
12049
|
+
});
|
|
12050
|
+
}
|
|
12051
|
+
else {
|
|
12052
|
+
// Create a dummy object if autocomplete is disabled
|
|
12053
|
+
this.ap = {
|
|
12054
|
+
list: [],
|
|
12055
|
+
close: () => { },
|
|
12056
|
+
evaluate: () => { }
|
|
12057
|
+
};
|
|
12058
|
+
}
|
|
12910
12059
|
let self = this;
|
|
12060
|
+
let noSuggest = false;
|
|
12061
|
+
let justAwesompleted = false;
|
|
12062
|
+
// Handle autocomplete selection (only if enabled)
|
|
12063
|
+
if (this.autocompleteEnabled) {
|
|
12064
|
+
input.node().addEventListener("awesomplete-selectcomplete", (event) => {
|
|
12065
|
+
noSuggest = true;
|
|
12066
|
+
const selectedValue = event.text.value;
|
|
12067
|
+
// Trigger search with selected value
|
|
12068
|
+
self.performDeepSearch(selectedValue);
|
|
12069
|
+
justAwesompleted = true;
|
|
12070
|
+
});
|
|
12071
|
+
}
|
|
12911
12072
|
input.on("keydown", (event) => {
|
|
12073
|
+
// Handle ESC key to clear the search box
|
|
12074
|
+
if (event.key === 'Escape') {
|
|
12075
|
+
const inputElement = event.target;
|
|
12076
|
+
inputElement.value = '';
|
|
12077
|
+
// Trigger input event to clear search results
|
|
12078
|
+
self.exitSearchMode();
|
|
12079
|
+
self.ap.close();
|
|
12080
|
+
clear.classed("tsi-shown", false);
|
|
12081
|
+
return;
|
|
12082
|
+
}
|
|
12912
12083
|
this.chartOptions.onKeydown(event, this.ap);
|
|
12913
12084
|
});
|
|
12914
|
-
|
|
12085
|
+
input.node().addEventListener("keyup", function (event) {
|
|
12086
|
+
if (justAwesompleted) {
|
|
12087
|
+
justAwesompleted = false;
|
|
12088
|
+
return;
|
|
12089
|
+
}
|
|
12090
|
+
let key = event.which || event.keyCode;
|
|
12091
|
+
if (key === 13) {
|
|
12092
|
+
noSuggest = true;
|
|
12093
|
+
}
|
|
12094
|
+
});
|
|
12095
|
+
// Debounced input handler to reduce work while typing
|
|
12915
12096
|
input.on("input", function (event) {
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
self.
|
|
12920
|
-
self.
|
|
12097
|
+
const val = event.target.value;
|
|
12098
|
+
// always clear existing timer
|
|
12099
|
+
if (self.debounceTimer) {
|
|
12100
|
+
clearTimeout(self.debounceTimer);
|
|
12101
|
+
self.debounceTimer = null;
|
|
12102
|
+
}
|
|
12103
|
+
// Show/hide clear button
|
|
12104
|
+
clear.classed("tsi-shown", val.length > 0);
|
|
12105
|
+
if (!val || val.length === 0) {
|
|
12106
|
+
// Exit search mode and restore navigation view
|
|
12107
|
+
self.exitSearchMode();
|
|
12108
|
+
self.ap.close();
|
|
12109
|
+
return;
|
|
12110
|
+
}
|
|
12111
|
+
// Populate autocomplete suggestions with instance leaves (only if enabled)
|
|
12112
|
+
if (self.autocompleteEnabled && !noSuggest && val.length >= 1) {
|
|
12113
|
+
self.fetchAutocompleteSuggestions(val);
|
|
12921
12114
|
}
|
|
12922
12115
|
else {
|
|
12923
|
-
|
|
12924
|
-
self.filterTree(searchText);
|
|
12116
|
+
self.ap.close();
|
|
12925
12117
|
}
|
|
12118
|
+
// Use deep search for comprehensive results
|
|
12119
|
+
self.debounceTimer = setTimeout(() => {
|
|
12120
|
+
self.performDeepSearch(val);
|
|
12121
|
+
}, self.debounceDelay);
|
|
12122
|
+
noSuggest = false;
|
|
12926
12123
|
});
|
|
12927
12124
|
}
|
|
12928
12125
|
async pathSearchAndRenderResult({ search: { payload, bubbleUpReject = false }, render: { target, locInTarget = null } }) {
|
|
12126
|
+
const requestId = ++this.requestCounter;
|
|
12127
|
+
this.latestRequestId = requestId;
|
|
12929
12128
|
try {
|
|
12930
12129
|
const result = await this.searchFunction(payload);
|
|
12130
|
+
if (requestId !== this.latestRequestId) {
|
|
12131
|
+
return;
|
|
12132
|
+
}
|
|
12931
12133
|
if (result.error) {
|
|
12932
12134
|
throw result.error;
|
|
12933
12135
|
}
|
|
12934
|
-
this.renderSearchResult(result, payload, target);
|
|
12136
|
+
await this.renderSearchResult(result, payload, target);
|
|
12935
12137
|
}
|
|
12936
12138
|
catch (err) {
|
|
12139
|
+
if (requestId !== this.latestRequestId) {
|
|
12140
|
+
return;
|
|
12141
|
+
}
|
|
12937
12142
|
this.chartOptions.onError("Error in hierarchy navigation", "Failed to complete search", err instanceof XMLHttpRequest ? err : null);
|
|
12938
12143
|
if (bubbleUpReject) {
|
|
12939
12144
|
throw err;
|
|
@@ -12941,11 +12146,18 @@ class HierarchyNavigation extends Component {
|
|
|
12941
12146
|
}
|
|
12942
12147
|
}
|
|
12943
12148
|
filterTree(searchText) {
|
|
12944
|
-
|
|
12945
|
-
|
|
12149
|
+
const nodes = this.hierarchyElem.selectAll('ul').nodes();
|
|
12150
|
+
if (!nodes || !nodes.length)
|
|
12151
|
+
return;
|
|
12152
|
+
const tree = nodes[0];
|
|
12153
|
+
if (!tree)
|
|
12154
|
+
return;
|
|
12155
|
+
const list = tree.querySelectorAll('li');
|
|
12156
|
+
const needle = searchText.toLowerCase();
|
|
12946
12157
|
list.forEach((li) => {
|
|
12947
|
-
|
|
12948
|
-
|
|
12158
|
+
const attrName = li.getAttribute('data-display-name');
|
|
12159
|
+
let name = attrName && attrName.length ? attrName : (li.querySelector('.tsi-name')?.textContent || '');
|
|
12160
|
+
if (name.toLowerCase().includes(needle)) {
|
|
12949
12161
|
li.style.display = 'block';
|
|
12950
12162
|
}
|
|
12951
12163
|
else {
|
|
@@ -12953,11 +12165,300 @@ class HierarchyNavigation extends Component {
|
|
|
12953
12165
|
}
|
|
12954
12166
|
});
|
|
12955
12167
|
}
|
|
12168
|
+
// Fetch autocomplete suggestions for instances (leaves)
|
|
12169
|
+
async fetchAutocompleteSuggestions(searchText) {
|
|
12170
|
+
if (!searchText || searchText.length < 1) {
|
|
12171
|
+
this.ap.list = [];
|
|
12172
|
+
return;
|
|
12173
|
+
}
|
|
12174
|
+
try {
|
|
12175
|
+
// Call server search to get instance suggestions
|
|
12176
|
+
const payload = {
|
|
12177
|
+
path: this.path,
|
|
12178
|
+
searchTerm: searchText,
|
|
12179
|
+
recursive: true,
|
|
12180
|
+
includeInstances: true,
|
|
12181
|
+
// Limit results for autocomplete
|
|
12182
|
+
maxResults: 10
|
|
12183
|
+
};
|
|
12184
|
+
const results = await this.searchFunction(payload);
|
|
12185
|
+
if (results.error) {
|
|
12186
|
+
this.ap.list = [];
|
|
12187
|
+
return;
|
|
12188
|
+
}
|
|
12189
|
+
// Extract instance names for autocomplete suggestions
|
|
12190
|
+
const suggestions = [];
|
|
12191
|
+
if (results.instances?.hits) {
|
|
12192
|
+
results.instances.hits.forEach((i) => {
|
|
12193
|
+
const displayName = this.instanceNodeStringToDisplay(i);
|
|
12194
|
+
const pathStr = i.hierarchyPath && i.hierarchyPath.length > 0
|
|
12195
|
+
? i.hierarchyPath.join(' > ') + ' > '
|
|
12196
|
+
: '';
|
|
12197
|
+
suggestions.push({
|
|
12198
|
+
label: pathStr + displayName,
|
|
12199
|
+
value: displayName
|
|
12200
|
+
});
|
|
12201
|
+
});
|
|
12202
|
+
}
|
|
12203
|
+
// Update Awesomplete list
|
|
12204
|
+
this.ap.list = suggestions;
|
|
12205
|
+
}
|
|
12206
|
+
catch (err) {
|
|
12207
|
+
// Silently fail for autocomplete - don't interrupt user experience
|
|
12208
|
+
this.ap.list = [];
|
|
12209
|
+
}
|
|
12210
|
+
}
|
|
12211
|
+
// Perform deep search across entire hierarchy using server-side search
|
|
12212
|
+
async performDeepSearch(searchText) {
|
|
12213
|
+
if (!searchText || searchText.length < 2) {
|
|
12214
|
+
this.exitSearchMode();
|
|
12215
|
+
return;
|
|
12216
|
+
}
|
|
12217
|
+
this.isSearchMode = true;
|
|
12218
|
+
const requestId = ++this.requestCounter;
|
|
12219
|
+
this.latestRequestId = requestId;
|
|
12220
|
+
try {
|
|
12221
|
+
// Call server search with recursive flag
|
|
12222
|
+
const payload = {
|
|
12223
|
+
path: this.path,
|
|
12224
|
+
searchTerm: searchText,
|
|
12225
|
+
recursive: true, // Search entire subtree
|
|
12226
|
+
includeInstances: true
|
|
12227
|
+
};
|
|
12228
|
+
const results = await this.searchFunction(payload);
|
|
12229
|
+
if (requestId !== this.latestRequestId)
|
|
12230
|
+
return; // Stale request
|
|
12231
|
+
if (results.error) {
|
|
12232
|
+
throw results.error;
|
|
12233
|
+
}
|
|
12234
|
+
// Render search results in flat list view
|
|
12235
|
+
this.renderSearchResults(results, searchText);
|
|
12236
|
+
}
|
|
12237
|
+
catch (err) {
|
|
12238
|
+
if (requestId !== this.latestRequestId)
|
|
12239
|
+
return;
|
|
12240
|
+
this.chartOptions.onError("Search failed", "Unable to search hierarchy", err instanceof XMLHttpRequest ? err : null);
|
|
12241
|
+
}
|
|
12242
|
+
}
|
|
12243
|
+
// Render search results with breadcrumb paths
|
|
12244
|
+
renderSearchResults(results, searchText) {
|
|
12245
|
+
this.hierarchyElem.selectAll('*').remove();
|
|
12246
|
+
const flatResults = [];
|
|
12247
|
+
// Flatten hierarchy results with full paths
|
|
12248
|
+
if (results.hierarchyNodes?.hits) {
|
|
12249
|
+
results.hierarchyNodes.hits.forEach((h) => {
|
|
12250
|
+
flatResults.push({
|
|
12251
|
+
type: 'hierarchy',
|
|
12252
|
+
name: h.name,
|
|
12253
|
+
path: h.path || [],
|
|
12254
|
+
id: h.id,
|
|
12255
|
+
cumulativeInstanceCount: h.cumulativeInstanceCount,
|
|
12256
|
+
highlightedName: this.highlightMatch(h.name, searchText),
|
|
12257
|
+
node: h
|
|
12258
|
+
});
|
|
12259
|
+
});
|
|
12260
|
+
}
|
|
12261
|
+
// Flatten instance results with full paths
|
|
12262
|
+
if (results.instances?.hits) {
|
|
12263
|
+
results.instances.hits.forEach((i) => {
|
|
12264
|
+
const displayName = this.instanceNodeStringToDisplay(i);
|
|
12265
|
+
flatResults.push({
|
|
12266
|
+
type: 'instance',
|
|
12267
|
+
name: i.name,
|
|
12268
|
+
path: i.hierarchyPath || [],
|
|
12269
|
+
id: i.id,
|
|
12270
|
+
timeSeriesId: i.timeSeriesId,
|
|
12271
|
+
description: i.description,
|
|
12272
|
+
highlightedName: this.highlightMatch(displayName, searchText),
|
|
12273
|
+
node: i
|
|
12274
|
+
});
|
|
12275
|
+
});
|
|
12276
|
+
}
|
|
12277
|
+
// Render flat list with breadcrumbs
|
|
12278
|
+
const searchList = this.hierarchyElem
|
|
12279
|
+
.append('div')
|
|
12280
|
+
.classed('tsi-search-results', true);
|
|
12281
|
+
if (flatResults.length === 0) {
|
|
12282
|
+
searchList.append('div')
|
|
12283
|
+
.classed('tsi-noResults', true)
|
|
12284
|
+
.text(this.getString('No results'));
|
|
12285
|
+
return;
|
|
12286
|
+
}
|
|
12287
|
+
searchList.append('div')
|
|
12288
|
+
.classed('tsi-search-results-header', true)
|
|
12289
|
+
.html(`<strong>${flatResults.length}</strong> ${this.getString('results found') || 'results found'}`);
|
|
12290
|
+
const resultItems = searchList.selectAll('.tsi-search-result-item')
|
|
12291
|
+
.data(flatResults)
|
|
12292
|
+
.enter()
|
|
12293
|
+
.append('div')
|
|
12294
|
+
.classed('tsi-search-result-item', true)
|
|
12295
|
+
.attr('tabindex', '0')
|
|
12296
|
+
.attr('role', 'option')
|
|
12297
|
+
.attr('aria-label', (d) => {
|
|
12298
|
+
const pathStr = d.path.length > 0 ? d.path.join(' > ') + ' > ' : '';
|
|
12299
|
+
return pathStr + d.name;
|
|
12300
|
+
});
|
|
12301
|
+
const self = this;
|
|
12302
|
+
resultItems.each(function (d) {
|
|
12303
|
+
const item = d3__namespace.select(this);
|
|
12304
|
+
// Breadcrumb path
|
|
12305
|
+
if (d.path.length > 0) {
|
|
12306
|
+
item.append('div')
|
|
12307
|
+
.classed('tsi-search-breadcrumb', true)
|
|
12308
|
+
.text(d.path.join(' > '));
|
|
12309
|
+
}
|
|
12310
|
+
// Highlighted name
|
|
12311
|
+
item.append('div')
|
|
12312
|
+
.classed('tsi-search-result-name', true)
|
|
12313
|
+
.html(d.highlightedName);
|
|
12314
|
+
// Instance description or count
|
|
12315
|
+
if (d.type === 'instance' && d.description) {
|
|
12316
|
+
item.append('div')
|
|
12317
|
+
.classed('tsi-search-result-description', true)
|
|
12318
|
+
.text(d.description);
|
|
12319
|
+
}
|
|
12320
|
+
else if (d.type === 'hierarchy') {
|
|
12321
|
+
item.append('div')
|
|
12322
|
+
.classed('tsi-search-result-count', true)
|
|
12323
|
+
.text(`${d.cumulativeInstanceCount || 0} instances`);
|
|
12324
|
+
}
|
|
12325
|
+
});
|
|
12326
|
+
// Click handlers
|
|
12327
|
+
resultItems.on('click keydown', function (event, d) {
|
|
12328
|
+
if (Utils.isKeyDownAndNotEnter(event))
|
|
12329
|
+
return;
|
|
12330
|
+
if (d.type === 'instance') {
|
|
12331
|
+
// Handle instance selection
|
|
12332
|
+
if (self.chartOptions.onInstanceClick) {
|
|
12333
|
+
const inst = new InstanceNode(d.timeSeriesId, d.name, d.path.length, d.id, d.description);
|
|
12334
|
+
// Update selection state
|
|
12335
|
+
if (self.selectedIds && self.selectedIds.includes(d.id)) {
|
|
12336
|
+
self.selectedIds = self.selectedIds.filter(id => id !== d.id);
|
|
12337
|
+
d3__namespace.select(this).classed('tsi-selected', false);
|
|
12338
|
+
}
|
|
12339
|
+
else {
|
|
12340
|
+
self.selectedIds.push(d.id);
|
|
12341
|
+
d3__namespace.select(this).classed('tsi-selected', true);
|
|
12342
|
+
}
|
|
12343
|
+
self.chartOptions.onInstanceClick(inst);
|
|
12344
|
+
}
|
|
12345
|
+
}
|
|
12346
|
+
else {
|
|
12347
|
+
// Navigate to hierarchy node - exit search and expand to that path
|
|
12348
|
+
self.navigateToPath(d.path);
|
|
12349
|
+
}
|
|
12350
|
+
});
|
|
12351
|
+
// Apply selection state to already-selected instances
|
|
12352
|
+
resultItems.each(function (d) {
|
|
12353
|
+
if (d.type === 'instance' && self.selectedIds && self.selectedIds.includes(d.id)) {
|
|
12354
|
+
d3__namespace.select(this).classed('tsi-selected', true);
|
|
12355
|
+
}
|
|
12356
|
+
});
|
|
12357
|
+
}
|
|
12358
|
+
// Exit search mode and restore tree
|
|
12359
|
+
exitSearchMode() {
|
|
12360
|
+
this.isSearchMode = false;
|
|
12361
|
+
this.hierarchyElem.selectAll('*').remove();
|
|
12362
|
+
this.pathSearchAndRenderResult({
|
|
12363
|
+
search: { payload: this.requestPayload() },
|
|
12364
|
+
render: { target: this.hierarchyElem }
|
|
12365
|
+
});
|
|
12366
|
+
}
|
|
12367
|
+
// Navigate to a specific path in the hierarchy
|
|
12368
|
+
async navigateToPath(targetPath) {
|
|
12369
|
+
this.exitSearchMode();
|
|
12370
|
+
// For now, just exit search mode and return to root
|
|
12371
|
+
// In a more advanced implementation, this would progressively
|
|
12372
|
+
// expand nodes along the path to reveal the target
|
|
12373
|
+
// This would require waiting for each level to load before expanding the next
|
|
12374
|
+
}
|
|
12375
|
+
// Pre-compute which paths need to be auto-expanded for preselected instances
|
|
12376
|
+
async computePathsToAutoExpand(instanceIds) {
|
|
12377
|
+
if (!instanceIds || instanceIds.length === 0) {
|
|
12378
|
+
return;
|
|
12379
|
+
}
|
|
12380
|
+
// console.log('[HierarchyNavigation] Computing paths to auto-expand for:', instanceIds);
|
|
12381
|
+
try {
|
|
12382
|
+
this.pathsToAutoExpand.clear();
|
|
12383
|
+
for (const instanceId of instanceIds) {
|
|
12384
|
+
// Search for this specific instance
|
|
12385
|
+
const result = await this.searchFunction({
|
|
12386
|
+
path: this.path,
|
|
12387
|
+
searchTerm: instanceId,
|
|
12388
|
+
recursive: true,
|
|
12389
|
+
includeInstances: true
|
|
12390
|
+
});
|
|
12391
|
+
if (result?.instances?.hits) {
|
|
12392
|
+
for (const instance of result.instances.hits) {
|
|
12393
|
+
// Match by ID
|
|
12394
|
+
if (instance.id === instanceId ||
|
|
12395
|
+
(instance.id && instance.id.includes(instanceId))) {
|
|
12396
|
+
if (instance.hierarchyPath && instance.hierarchyPath.length > 0) {
|
|
12397
|
+
// Add all parent paths that need to be expanded
|
|
12398
|
+
const hierarchyPath = instance.hierarchyPath;
|
|
12399
|
+
for (let i = 1; i <= hierarchyPath.length; i++) {
|
|
12400
|
+
const pathArray = hierarchyPath.slice(0, i);
|
|
12401
|
+
const pathKey = pathArray.join('/');
|
|
12402
|
+
this.pathsToAutoExpand.add(pathKey);
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
}
|
|
12406
|
+
}
|
|
12407
|
+
}
|
|
12408
|
+
}
|
|
12409
|
+
// console.log('[HierarchyNavigation] Paths to auto-expand:', Array.from(this.pathsToAutoExpand));
|
|
12410
|
+
}
|
|
12411
|
+
catch (err) {
|
|
12412
|
+
console.warn('Failed to compute paths to auto-expand:', err);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
// Check if a path should be auto-expanded
|
|
12416
|
+
shouldAutoExpand(pathArray) {
|
|
12417
|
+
if (this.pathsToAutoExpand.size === 0) {
|
|
12418
|
+
return false;
|
|
12419
|
+
}
|
|
12420
|
+
const pathKey = pathArray.join('/');
|
|
12421
|
+
return this.pathsToAutoExpand.has(pathKey);
|
|
12422
|
+
}
|
|
12423
|
+
// Auto-expand a node by triggering its expand function
|
|
12424
|
+
async autoExpandNode(node) {
|
|
12425
|
+
if (!node || !node.expand || !node.node) {
|
|
12426
|
+
return;
|
|
12427
|
+
}
|
|
12428
|
+
try {
|
|
12429
|
+
// Wait for the DOM node to be available
|
|
12430
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
12431
|
+
// Mark as expanded visually
|
|
12432
|
+
node.node.classed('tsi-expanded', true);
|
|
12433
|
+
// Call the expand function to load children
|
|
12434
|
+
await node.expand();
|
|
12435
|
+
// console.log(`[HierarchyNavigation] Auto-expanded node: ${node.path.join('/')}`);
|
|
12436
|
+
}
|
|
12437
|
+
catch (err) {
|
|
12438
|
+
console.warn(`Failed to auto-expand node ${node.path.join('/')}:`, err);
|
|
12439
|
+
}
|
|
12440
|
+
}
|
|
12441
|
+
// Highlight search term in text
|
|
12442
|
+
highlightMatch(text, searchTerm) {
|
|
12443
|
+
if (!text)
|
|
12444
|
+
return '';
|
|
12445
|
+
const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12446
|
+
const regex = new RegExp(`(${escapedTerm})`, 'gi');
|
|
12447
|
+
return text.replace(regex, '<mark>$1</mark>');
|
|
12448
|
+
}
|
|
12956
12449
|
// creates in-depth data object using the server response for hierarchyNodes to show in the tree all expanded, considering UntilChildren
|
|
12957
12450
|
fillDataRecursively(hierarchyNodes, payload, payloadForContinuation = null) {
|
|
12958
12451
|
let data = {};
|
|
12959
12452
|
hierarchyNodes.hits.forEach((h) => {
|
|
12960
12453
|
let hierarchy = new HierarchyNode(h.name, payload.path, payload.path.length - this.path.length, h.cumulativeInstanceCount, h.id);
|
|
12454
|
+
// cache display name on node for client-side filtering
|
|
12455
|
+
hierarchy.displayName = h.name || '';
|
|
12456
|
+
// Check if this path should be auto-expanded
|
|
12457
|
+
const shouldExpand = this.shouldAutoExpand(hierarchy.path);
|
|
12458
|
+
if (shouldExpand) {
|
|
12459
|
+
hierarchy.isExpanded = true;
|
|
12460
|
+
//console.log(`[HierarchyNavigation] Auto-expanding node: ${hierarchy.path.join('/')}`);
|
|
12461
|
+
}
|
|
12961
12462
|
hierarchy.expand = () => {
|
|
12962
12463
|
hierarchy.isExpanded = true;
|
|
12963
12464
|
hierarchy.node.classed('tsi-expanded', true);
|
|
@@ -12981,7 +12482,7 @@ class HierarchyNavigation extends Component {
|
|
|
12981
12482
|
.attr('style', `padding-left: ${hORi.isLeaf ? hORi.level * 18 + 20 : (hORi.level + 1) * 18 + 20}px`)
|
|
12982
12483
|
.attr('tabindex', 0)
|
|
12983
12484
|
//.attr('arialabel', isHierarchyNode ? key : Utils.getTimeSeriesIdString(hORi))
|
|
12984
|
-
.attr('
|
|
12485
|
+
.attr('aria-label', isHierarchyNode ? key : self.getAriaLabel(hORi))
|
|
12985
12486
|
.attr('title', isHierarchyNode ? key : self.getAriaLabel(hORi))
|
|
12986
12487
|
.attr("role", "treeitem").attr('aria-expanded', hORi.isExpanded)
|
|
12987
12488
|
.on('click keydown', async function (event) {
|
|
@@ -13033,6 +12534,8 @@ class HierarchyNavigation extends Component {
|
|
|
13033
12534
|
return hORi.description || hORi.name || hORi.id || Utils.getTimeSeriesIdString(hORi);
|
|
13034
12535
|
}
|
|
13035
12536
|
}
|
|
12537
|
+
// TreeRenderer has been moved to its own module: ./TreeRenderer
|
|
12538
|
+
// The rendering logic was extracted to reduce file size and improve testability.
|
|
13036
12539
|
class HierarchyNode {
|
|
13037
12540
|
constructor(name, parentPath, level, cumulativeInstanceCount = null, id = null) {
|
|
13038
12541
|
this.name = name;
|
|
@@ -13277,6 +12780,7 @@ class SingleDateTimePicker extends ChartComponent {
|
|
|
13277
12780
|
class DateTimeButtonSingle extends DateTimeButton {
|
|
13278
12781
|
constructor(renderTarget) {
|
|
13279
12782
|
super(renderTarget);
|
|
12783
|
+
this.clickOutsideHandler = null;
|
|
13280
12784
|
this.sDTPOnSet = (millis = null) => {
|
|
13281
12785
|
if (millis !== null) {
|
|
13282
12786
|
this.dateTimeButton.text(this.buttonDateTimeFormat(millis));
|
|
@@ -13289,6 +12793,32 @@ class DateTimeButtonSingle extends DateTimeButton {
|
|
|
13289
12793
|
closeSDTP() {
|
|
13290
12794
|
this.dateTimePickerContainer.style("display", "none");
|
|
13291
12795
|
this.dateTimeButton.node().focus();
|
|
12796
|
+
this.removeClickOutsideHandler();
|
|
12797
|
+
}
|
|
12798
|
+
removeClickOutsideHandler() {
|
|
12799
|
+
if (this.clickOutsideHandler) {
|
|
12800
|
+
document.removeEventListener('click', this.clickOutsideHandler);
|
|
12801
|
+
this.clickOutsideHandler = null;
|
|
12802
|
+
}
|
|
12803
|
+
}
|
|
12804
|
+
setupClickOutsideHandler() {
|
|
12805
|
+
// Remove any existing handler first
|
|
12806
|
+
this.removeClickOutsideHandler();
|
|
12807
|
+
// Add handler after a small delay to prevent the opening click from immediately closing the picker
|
|
12808
|
+
setTimeout(() => {
|
|
12809
|
+
this.clickOutsideHandler = (event) => {
|
|
12810
|
+
const pickerElement = this.dateTimePickerContainer.node();
|
|
12811
|
+
const buttonElement = this.dateTimeButton.node();
|
|
12812
|
+
const target = event.target;
|
|
12813
|
+
// Check if click is outside both the picker and the button
|
|
12814
|
+
if (pickerElement && buttonElement &&
|
|
12815
|
+
!pickerElement.contains(target) &&
|
|
12816
|
+
!buttonElement.contains(target)) {
|
|
12817
|
+
this.closeSDTP();
|
|
12818
|
+
}
|
|
12819
|
+
};
|
|
12820
|
+
document.addEventListener('click', this.clickOutsideHandler);
|
|
12821
|
+
}, 0);
|
|
13292
12822
|
}
|
|
13293
12823
|
render(chartOptions = {}, minMillis, maxMillis, selectedMillis = null, onSet = null) {
|
|
13294
12824
|
super.render(chartOptions, minMillis, maxMillis, onSet);
|
|
@@ -13298,12 +12828,11 @@ class DateTimeButtonSingle extends DateTimeButton {
|
|
|
13298
12828
|
if (!this.dateTimePicker) {
|
|
13299
12829
|
this.dateTimePicker = new SingleDateTimePicker(this.dateTimePickerContainer.node());
|
|
13300
12830
|
}
|
|
13301
|
-
let targetElement = d3__namespace.select(this.renderTarget);
|
|
13302
|
-
(targetElement.select(".tsi-dateTimePickerContainer")).selectAll("*");
|
|
13303
12831
|
this.dateTimeButton.on("click", () => {
|
|
13304
12832
|
this.chartOptions.dTPIsModal = true;
|
|
13305
12833
|
this.dateTimePickerContainer.style("display", "block");
|
|
13306
12834
|
this.dateTimePicker.render(this.chartOptions, this.minMillis, this.maxMillis, this.selectedMillis, this.sDTPOnSet);
|
|
12835
|
+
this.setupClickOutsideHandler();
|
|
13307
12836
|
});
|
|
13308
12837
|
}
|
|
13309
12838
|
}
|
|
@@ -13601,8 +13130,16 @@ class ProcessGraphic extends HistoryPlayback {
|
|
|
13601
13130
|
class PlaybackControls extends Component {
|
|
13602
13131
|
constructor(renderTarget, initialTimeStamp = null) {
|
|
13603
13132
|
super(renderTarget);
|
|
13604
|
-
this.
|
|
13605
|
-
this.
|
|
13133
|
+
this.playbackInterval = null;
|
|
13134
|
+
this.playButton = null;
|
|
13135
|
+
this.handleElement = null;
|
|
13136
|
+
this.controlsContainer = null;
|
|
13137
|
+
this.track = null;
|
|
13138
|
+
this.selectTimeStampCallback = null;
|
|
13139
|
+
this.wasPlayingWhenDragStarted = false;
|
|
13140
|
+
this.rafId = null;
|
|
13141
|
+
this.handleRadius = PlaybackControls.CONSTANTS.HANDLE_RADIUS;
|
|
13142
|
+
this.minimumPlaybackInterval = PlaybackControls.CONSTANTS.MINIMUM_PLAYBACK_INTERVAL_MS;
|
|
13606
13143
|
this.playbackInterval = null;
|
|
13607
13144
|
this.selectedTimeStamp = initialTimeStamp;
|
|
13608
13145
|
}
|
|
@@ -13610,6 +13147,21 @@ class PlaybackControls extends Component {
|
|
|
13610
13147
|
return this.selectedTimeStamp;
|
|
13611
13148
|
}
|
|
13612
13149
|
render(start, end, onSelectTimeStamp, options, playbackSettings) {
|
|
13150
|
+
// Validate inputs
|
|
13151
|
+
if (!(start instanceof Date) || !(end instanceof Date)) {
|
|
13152
|
+
throw new TypeError('start and end must be Date objects');
|
|
13153
|
+
}
|
|
13154
|
+
if (start >= end) {
|
|
13155
|
+
throw new RangeError('start must be before end');
|
|
13156
|
+
}
|
|
13157
|
+
if (!onSelectTimeStamp || typeof onSelectTimeStamp !== 'function') {
|
|
13158
|
+
throw new TypeError('onSelectTimeStamp must be a function');
|
|
13159
|
+
}
|
|
13160
|
+
// Clean up any pending animation frames before re-rendering
|
|
13161
|
+
if (this.rafId !== null) {
|
|
13162
|
+
cancelAnimationFrame(this.rafId);
|
|
13163
|
+
this.rafId = null;
|
|
13164
|
+
}
|
|
13613
13165
|
this.end = end;
|
|
13614
13166
|
this.selectTimeStampCallback = onSelectTimeStamp;
|
|
13615
13167
|
this.chartOptions.setOptions(options);
|
|
@@ -13671,6 +13223,9 @@ class PlaybackControls extends Component {
|
|
|
13671
13223
|
this.playButton = this.controlsContainer.append('button')
|
|
13672
13224
|
.classed('tsi-play-button', this.playbackInterval === null)
|
|
13673
13225
|
.classed('tsi-pause-button', this.playbackInterval !== null)
|
|
13226
|
+
// Accessibility attributes
|
|
13227
|
+
.attr('aria-label', 'Play/Pause playback')
|
|
13228
|
+
.attr('title', 'Play/Pause playback')
|
|
13674
13229
|
.on('click', () => {
|
|
13675
13230
|
if (this.playbackInterval === null) {
|
|
13676
13231
|
this.play();
|
|
@@ -13722,6 +13277,27 @@ class PlaybackControls extends Component {
|
|
|
13722
13277
|
this.updateSelection(handlePosition, this.selectedTimeStamp);
|
|
13723
13278
|
this.selectTimeStampCallback(this.selectedTimeStamp);
|
|
13724
13279
|
}
|
|
13280
|
+
/**
|
|
13281
|
+
* Cleanup resources to prevent memory leaks
|
|
13282
|
+
*/
|
|
13283
|
+
destroy() {
|
|
13284
|
+
this.pause();
|
|
13285
|
+
// Cancel any pending animation frames
|
|
13286
|
+
if (this.rafId !== null) {
|
|
13287
|
+
cancelAnimationFrame(this.rafId);
|
|
13288
|
+
this.rafId = null;
|
|
13289
|
+
}
|
|
13290
|
+
// Remove event listeners
|
|
13291
|
+
if (this.controlsContainer) {
|
|
13292
|
+
this.controlsContainer.selectAll('*').on('.', null);
|
|
13293
|
+
}
|
|
13294
|
+
// Clear DOM references
|
|
13295
|
+
this.playButton = null;
|
|
13296
|
+
this.handleElement = null;
|
|
13297
|
+
this.controlsContainer = null;
|
|
13298
|
+
this.track = null;
|
|
13299
|
+
this.selectTimeStampCallback = null;
|
|
13300
|
+
}
|
|
13725
13301
|
clamp(number, min, max) {
|
|
13726
13302
|
let clamped = Math.max(number, min);
|
|
13727
13303
|
return Math.min(clamped, max);
|
|
@@ -13730,9 +13306,17 @@ class PlaybackControls extends Component {
|
|
|
13730
13306
|
this.wasPlayingWhenDragStarted = this.wasPlayingWhenDragStarted ||
|
|
13731
13307
|
(this.playbackInterval !== null);
|
|
13732
13308
|
this.pause();
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
this.
|
|
13309
|
+
// Use requestAnimationFrame to batch DOM updates for better performance
|
|
13310
|
+
// Cancel any pending animation frame to prevent stacking updates
|
|
13311
|
+
if (this.rafId !== null) {
|
|
13312
|
+
cancelAnimationFrame(this.rafId);
|
|
13313
|
+
}
|
|
13314
|
+
this.rafId = requestAnimationFrame(() => {
|
|
13315
|
+
const handlePosition = this.clamp(positionX, 0, this.trackWidth);
|
|
13316
|
+
this.selectedTimeStamp = this.timeStampToPosition.invert(handlePosition);
|
|
13317
|
+
this.updateSelection(handlePosition, this.selectedTimeStamp);
|
|
13318
|
+
this.rafId = null;
|
|
13319
|
+
});
|
|
13736
13320
|
}
|
|
13737
13321
|
onDragEnd() {
|
|
13738
13322
|
this.selectTimeStampCallback(this.selectedTimeStamp);
|
|
@@ -13755,6 +13339,12 @@ class PlaybackControls extends Component {
|
|
|
13755
13339
|
.text(this.timeFormatter(timeStamp));
|
|
13756
13340
|
}
|
|
13757
13341
|
}
|
|
13342
|
+
PlaybackControls.CONSTANTS = {
|
|
13343
|
+
HANDLE_RADIUS: 7,
|
|
13344
|
+
MINIMUM_PLAYBACK_INTERVAL_MS: 1000,
|
|
13345
|
+
HANDLE_PADDING: 8,
|
|
13346
|
+
AXIS_OFFSET: 6,
|
|
13347
|
+
};
|
|
13758
13348
|
class TimeAxis extends TemporalXAxisComponent {
|
|
13759
13349
|
constructor(renderTarget) {
|
|
13760
13350
|
super(renderTarget);
|