Home Identifier Source Repository

src/polyfills/details/Element.details.js

// Originally from https://github.com/termi/Element.details
// HTMLElement.prototype.insertAdjacentHTML = https://gist.github.com/1276030

import forEach from 'lodash/collection/forEach'

// Chrome 10 will fail this detection, but Chrome 10 no longer exists
let support = 'open' in document.createElement('details')

function insertStyles() {
	// style
	document.head.insertAdjacentHTML('beforeend', '<br><style>' + // <br> need for all IE
		'details{display:block}' +
		'details>*{display:none}' +
		'details>summary,details>summary,details>.▼▼{display:block}' +
		'details .details-marker:before{content:"►"}' +
		'details.▼ .details-marker:before{content:"▼"}' +
		'details.▼>*{display:block}' +
	'</style>')
}

// event
function eventDetailClick(ev) {
	if (ev.detail === 0) {
		// Opera generate 'click' event with `detail` == 0 together with 'keyup' event
		return
	}

	// 32 - space. Need this ???
	// 13 - Enter.

	if (ev.keyCode === 13 || /*ev.type == 'keyup'*/ ev.type === 'click') {
		this.parentNode.open = !this.parentNode.open
	}
}

// details shim
function detailsShim(details) {
	if (details._ && details._._isShimmed) {
		return
	}

	if (!details._) {
		details._ = {}
	}

	// Wrap text node's and found `summary`
	let summary = undefined
	forEach(details.childNodes, child => {
		if (child.nodeType === 3 && /[^\t\n\r ]/.test(child.data)) {
			details.insertBefore(
				document.createElement('x-i'), // Create a fake inline element
				child).innerHTML = child.data

			details.removeChild(child)
		}
		else if (child.nodeName.toUpperCase() === 'SUMMARY') {
			summary = child
		}
	})

	// Create a fake 'summary' element
	if (!summary) {
		summary = document.createElement('x-s')
		summary.innerHTML = 'Details'
		summary.className = '▼▼' // http://css-tricks.com/unicode-class-names/
	}

	// Put summary as a first child
	details.insertBefore(summary, details.childNodes[0])
	// Create `details-marker` and put it as a summary first child
	summary.insertBefore(document.createElement('x-i'), summary.childNodes[0])
		.className = 'details-marker'

	// For access from keyboard
	summary.tabIndex = 0

	// events
	summary.addEventListener('click', eventDetailClick, false)
	summary.addEventListener('keyup', eventDetailClick, false)

	// flag to avoid double shim
	details._._isShimmed = true
}

// property 'open'
let openProperty = {
	get: function() {
		if (!('nodeName' in this) || this.nodeName.toUpperCase() !== 'DETAILS') {
			return void 0
		}

		return this.hasAttribute('open')
	},
	set: function(booleanValue) {
		if (!('nodeName' in this) || this.nodeName.toUpperCase() !== 'DETAILS') {
			return void 0
		}

		detailsShim(this)

		this.classList[booleanValue ? 'add' : 'remove']('▼')
		this[booleanValue ? 'setAttribute' : 'removeAttribute']('open', 'open')

		return booleanValue
	},
}

// init
function init() {
	// property 'open'
	Object.defineProperty(window.Element.prototype, 'open', openProperty)

	let detailses = document.getElementsByTagName('details')
	forEach(detailses, details => {
		// DOM API
		details.open = details.hasAttribute('open')
	})
}

function start() {
	if (!support) {
		insertStyles()

		// auto init
		if (document.readyState !== 'complete') {
			document.addEventListener('DOMContentLoaded', init, false)
		}
		else {
			init(window)
		}
	}
	// else {
		// TODO: for animation and other stuff we need to listen 'open'
		// property change and add 'open' css class for <details> element
	// }
}

export default start