import { queryOne } from '@ecl/dom-utils';
/**
* @param {HTMLElement} element DOM element for component instantiation and scope
* @param {Object} options
* @param {String} options.buttonSelector
* @param {String} options.labelSelector
* @param {String} options.labelExpanded
* @param {String} options.labelCollapsed
* @param {Boolean} options.attachClickListener Whether or not to bind click events
*/
export class Timeline {
/**
* @static
* Shorthand for instance creation and initialisation.
*
* @param {HTMLElement} root DOM element for component instantiation and scope
*
* @return {Timeline} An instance of Timeline.
*/
static autoInit(root, { TIMELINE: defaultOptions = {} } = {}) {
const timeline = new Timeline(root, defaultOptions);
timeline.init();
root.ECLTimeline = timeline;
return timeline;
}
constructor(
element,
{
buttonSelector = '[data-ecl-timeline-button]',
labelSelector = '[data-ecl-label]',
labelExpanded = 'data-ecl-label-expanded',
labelCollapsed = 'data-ecl-label-collapsed',
attachClickListener = true,
} = {},
) {
// Check element
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError(
'DOM element should be given to initialize this widget.',
);
}
this.element = element;
// Options
this.buttonSelector = buttonSelector;
this.labelSelector = labelSelector;
this.labelExpanded = labelExpanded;
this.labelCollapsed = labelCollapsed;
this.attachClickListener = attachClickListener;
// Private variables
this.button = null;
this.label = null;
// Bind `this` for use in callbacks
this.handleClickOnButton = this.handleClickOnButton.bind(this);
}
/**
* Initialise component.
*/
init() {
if (!ECL) {
throw new TypeError('Called init but ECL is not present');
}
ECL.components = ECL.components || new Map();
// Query elements
this.button = queryOne(this.buttonSelector, this.element);
// Get label, if any
this.label = queryOne(this.labelSelector, this.element);
// Bind click event on button
if (this.attachClickListener && this.button) {
this.button.addEventListener('click', this.handleClickOnButton);
}
// Set ecl initialized attribute
this.element.setAttribute('data-ecl-auto-initialized', 'true');
ECL.components.set(this.element, this);
}
/**
* Destroy component.
*/
destroy() {
if (this.attachClickListener && this.button) {
this.button.removeEventListener('click', this.handleClickOnButton);
}
if (this.element) {
this.element.removeAttribute('data-ecl-auto-initialized');
ECL.components.delete(this.element);
}
}
/**
* Expand timeline if not such already.
*/
handleClickOnButton() {
// Get current status
const isExpanded = this.button.getAttribute('aria-expanded') === 'true';
// Toggle the expandable/collapsible
this.button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
if (isExpanded) {
this.element.removeAttribute('data-ecl-timeline-expanded');
// Scroll up to the button
this.button.blur();
this.button.focus();
} else {
this.element.setAttribute('data-ecl-timeline-expanded', 'true');
// Focus first expanded item
const item = queryOne('.ecl-timeline__item--collapsed', this.element);
if (item) {
item.setAttribute('tabindex', '-1');
item.focus({ focusVisible: false });
item.removeAttribute('tabindex');
}
}
// Toggle label if possible
if (
this.label &&
!isExpanded &&
this.button.hasAttribute(this.labelExpanded)
) {
this.label.innerHTML = this.button.getAttribute(this.labelExpanded);
} else if (
this.label &&
isExpanded &&
this.button.hasAttribute(this.labelCollapsed)
) {
this.label.innerHTML = this.button.getAttribute(this.labelCollapsed);
}
return this;
}
}
export default Timeline;