diff --git a/components/mip-accordion/example/mip-accordion-manual.html b/components/mip-accordion/example/mip-accordion-manual.html new file mode 100644 index 00000000..e9514ff5 --- /dev/null +++ b/components/mip-accordion/example/mip-accordion-manual.html @@ -0,0 +1,38 @@ + + + + + + MIP page + + + + + + +
+

下拉第一个

+

我说你是人间的四月天;笑声点亮了四面风;轻灵在春的光艳中交舞着变。你是四月早天里的云烟,黄昏吹着风的软,星子在无意中闪,

+
+
+

下拉第二个

+

细雨点洒在花前。那轻,那娉婷,你是,鲜妍百花的冠冕你戴着,你是天真,庄严,你是夜夜的月圆。

+
+
+

下拉第三个

+ +
+
+ + + + diff --git a/components/mip-accordion/example/mip-accordion-title.html b/components/mip-accordion/example/mip-accordion-title.html new file mode 100644 index 00000000..416a7455 --- /dev/null +++ b/components/mip-accordion/example/mip-accordion-title.html @@ -0,0 +1,52 @@ + + + + + + MIP page + + + + + + +
+

下拉第一个

+

我说你是人间的四月天;笑声点亮了四面风;轻灵在春的光艳中交舞着变。你是四月早天里的云烟,黄昏吹着风的软,星子在无意中闪,

+
+
+

+
显示更多
+
折叠
+

+
+
次级内容
+
+

+
显示更多
+
折叠
+

+
+

细雨点洒在花前。那轻,那娉婷,你是,鲜妍百花的冠冕你戴着,你是天真,庄严,你是夜夜的月圆。

+
+
+
+
+
+

下拉第三个

+ +
+
+ + + + diff --git a/components/mip-accordion/mip-accordion.js b/components/mip-accordion/mip-accordion.js index e6ffca13..9d628d30 100644 --- a/components/mip-accordion/mip-accordion.js +++ b/components/mip-accordion/mip-accordion.js @@ -64,7 +64,7 @@ const OPEN_STATUS = 'open' * @constant * @type {string} */ -const CLOSE_STATUS = 'close' +// const CLOSE_STATUS = 'close' /** * 设置 session storage 缓存 @@ -108,7 +108,7 @@ function getSession (key) { * "transitionTime": "0.3", // seconds, animation time * "tarHeight": "140px", // DOM height when animation ends * "oriHeight": "20px", // DOM height when animation begins - * "cbFun": function() {}.bind() //callback, exec after animation + * "cb": function() {}.bind() //callback, exec after animation * } */ function heightAni (opt) { @@ -119,10 +119,10 @@ function heightAni (opt) { return } - let transitionTime = isNaN(opt.transitionTime) ? 0.24 : Math.min(parseFloat(opt.transitionTime), 1) + let transitionTime = opt.transitionTime let oriHeight = (opt.oriHeight !== undefined ? opt.oriHeight : getComputedStyle(element).height) let tarHeight - let cbFun = opt.cbFun || function () {} + let cb = opt.cb || function () {} if (type === 'unfold') { if (opt.tarHeight !== undefined) { @@ -150,29 +150,14 @@ function heightAni (opt) { }, 10) // 动画结束后,执行 callback - setTimeout(cbFun, transitionTime * 1000) + setTimeout(cb, transitionTime * 1000) } -// /** -// * 获取元素的兄弟节点 -// * -// * @param {HTMLElement} el 源 DOM 元素 -// * @returns {HTMLElement} 目标 DOM 元素 -// */ -// function getSibling (el) { -// el = el.nextSibling -// while (el.nodeType === 3) { -// el = el.nextSibling -// } - -// return el -// } - /** * 获取组件 slot 下面的所有
,并且过滤掉嵌套 MIP 组件下面的 section,以免 MIP 组件嵌套造成相互影响 * * @param {HTMLElement} element 组件根节点 - * @return {Array.} selection 列表 + * @returns {Array.} selection 列表 */ function getSections (element) { const sections = element.querySelectorAll('section') @@ -194,7 +179,38 @@ function getSections (element) { return validSections } +/** + * 生成 session key + * + * @param {string} sessionId sessionId + * @returns {string} session key + */ +function getSessionKey (sessionId) { + return `MIP-${sessionId}-${location.href}` +} + +/** + * 处理动画时间 + * + * @param {string} aniTimeAttr 动画时间配置属性 + * @returns {number} 动画时间秒数 + */ +function getAniTime (aniTimeAttr) { + let time = parseFloat(aniTimeAttr, 10) + return isNaN(time) ? 0.24 : Math.min(time, 1) +} + export default class MIPAccordion extends CustomElement { + constructor (...args) { + super(...args) + + this.type = '' + this.sections = null + this.sessionKey = '' + this.aniTime = 0.24 + this.expandedLimit = false + } + /** * 允许预渲染 */ @@ -202,188 +218,215 @@ export default class MIPAccordion extends CustomElement { return true } - /** - * 组件渲染 - */ build () { - let element = this.element - let type = element.getAttribute('type') || 'automatic' - let sections = this.sections = getSections(element) - let sessionId = this.sessionId = element.getAttribute('sessions-key') - let sessionKey = this.sessionKey = `MIP-${sessionId}-${location.href}` - let currentState = getSession.apply(this) + // 初始化属性 + this.type = this.element.getAttribute('type') || 'automatic' + this.sections = getSections(this.element) + this.sessionId = this.element.getAttribute('sessions-key') + this.sessionKey = getSessionKey(this.sessionId) + + this.aniTime = getAniTime(this.element.getAttribute('animatetime')) + this.expandedLimit = this.element.hasAttribute('expanded-limit') - element.setAttribute('role', 'tablist') + this.element.setAttribute('role', 'tablist') - sections.forEach((section, index) => { + // 初始化节点属性,绑定事件 + this.sections.forEach((section, index) => { // let header = section.querySelector('[accordionbtn]') // let content = section.querySelector('[accordionbox]') + this.initAttr(section, index) + this.bindEvent(section) + }) + } + + /** + * 初始化 accordion 节点属性 + * + * @param {HTMLElement} section 容器 + * @param {number} index section 在全文的序号 + */ + initAttr (section, index) { + let header = this.getHeader(section) + let content = this.getContent(section) + + // 添加默认样式 + header && header.classList.add(MIP_ACCORDION_HEADER_CLASS) + content && content.classList.add(MIP_ACCORDION_CONTENT_CLASS) + + // 设置内容块 id + let contentId = content.getAttribute('id') + + if (!contentId) { + contentId = `MIP_${this.sessionId}_content_${index}` + content.setAttribute('id', contentId) + } + + header.setAttribute(ARIA_CONTROLS_ATTRIBUTE, contentId) + + // 根据组件配置和 session 状态初始化节点的展开和折叠 + let sectionDesc = { + section, + contentId, + animate: false + } - let header = section.children.item(0) - let content = section.children.item(1) - header && header.classList.add(MIP_ACCORDION_HEADER_CLASS) - content && content.classList.add(MIP_ACCORDION_CONTENT_CLASS) + // 手动控制或者自动根据用户操作控制 + if (this.type === 'manual' && section.hasAttribute(EXPANDED_ATTRIBUTE)) { + this.unfold(sectionDesc) + } else if (this.type === 'automatic') { + this.currentState = this.currentState || getSession(this.sessionKey) - let contentId = content.getAttribute('id') - if (!contentId) { - contentId = `MIP_${sessionId}_content_${index}` - content.setAttribute('id', contentId) + if (this.currentState[contentId]) { + this.unfold(sectionDesc) + } else if (this.currentState[contentId] === false) { + this.fold(sectionDesc) } + } + } - // tab 状态 [展开/收起] 判断 - if (currentState[contentId]) { - section.setAttribute(EXPANDED_ATTRIBUTE, '') - } else if (currentState[contentId] === false) { - section.removeAttribute(EXPANDED_ATTRIBUTE) + /** + * 绑定事件 + * + * @param {HTMLElement} section 节点容器 + */ + bindEvent (section) { + let header = this.getHeader(section) + let targetId = header.getAttribute(ARIA_CONTROLS_ATTRIBUTE) + + header.addEventListener('click', () => { + let expanded = section.getAttribute(EXPANDED_ATTRIBUTE) + + if (expanded === OPEN_STATUS) { + this.fold({ + section, + targetId + }) + return } - // 手动控制或者自动根据用户操作控制 - if (type === 'manual' && section.hasAttribute(EXPANDED_ATTRIBUTE)) { - content.setAttribute(ARIA_EXPANDED_ATTRIBUTE, OPEN_STATUS) - setSession(sessionKey, element.getAttribute(ARIA_CONTROLS_ATTRIBUTE), true) - } else if (type === 'automatic') { - content.setAttribute(ARIA_EXPANDED_ATTRIBUTE, section.hasAttribute(EXPANDED_ATTRIBUTE).toString()) + // 同时只能展开一个节点 + if (this.expandedLimit) { + // 折叠其他节点 + this.sections + .filter(otherSection => ( + otherSection !== section && + otherSection.hasAttribute(EXPANDED_ATTRIBUTE) + )) + .forEach(otherSection => { + this.fold({ + section: otherSection + }) + }) } - header.setAttribute(ARIA_CONTROLS_ATTRIBUTE, contentId) + this.unfold({ + section, + targetId + }) }) + } - if (type === 'automatic') { - this.userSelect() - } + /** + * 通过节点容器获取 header + * + * @param {HTMLElement} section 节点容器 + * @returns {HTMLElement} header + */ + getHeader (section) { + return section.children.item(0) + } - this.bindEvents() + /** + * 通过节点容器获取 content + * + * @param {HTMLElement} section 节点容器 + * @returns {HTMLElement} content + */ + getContent (section) { + return section.children.item(1) } /** - * 恢复用户上次的选择 + * 展开节点 + * + * @param {string=} targetId content 的节点 id + * @param {HTMLElement} section 节点容器 + * @param {boolean} animate 展开过程是否出动画 */ - userSelect () { - let ele = this.element - let sessionData = getSession(this.sessionKey) + unfold ({ + targetId, + section, + animate = true + }) { + section.setAttribute(EXPANDED_ATTRIBUTE, OPEN_STATUS) + + let content = this.getContent(section) + if (!content) { + return + } - for (let prop in sessionData) { - if (!sessionData.hasOwnProperty(prop)) { - return - } + content.setAttribute(ARIA_EXPANDED_ATTRIBUTE, OPEN_STATUS) - if (sessionData[prop]) { - let content = ele.querySelector('#' + prop) - content.setAttribute(ARIA_EXPANDED_ATTRIBUTE, OPEN_STATUS) - let parent = content.parentNode - while (parent !== ele) { - if (parent.tagName === 'section') { - parent.setAttribute(EXPANDED_ATTRIBUTE, OPEN_STATUS) - } - parent = parent.parentNode - } - // ele.querySelector('section').setAttribute(EXPANDED_ATTRIBUTE, OPEN_STATUS) - } + // 记录折叠情况 + if (this.type === 'automatic') { + targetId = targetId || content.getAttribute('id') + setSession(this.sessionKey, targetId, true) + } + + // 折叠过程是否出动画效果 + if (!animate) { + return } + + heightAni({ + ele: content, + type: 'unfold', + oriHeight: 0, + transitionTime: this.aniTime, + cb () { + util.css(content, 'height', '') + } + }) } /** - * 绑定事件 + * 折叠节点 + * + * @param {string=} targetId content 的节点 id + * @param {HTMLElement} section 节点容器 + * @param {boolean} animate 展开过程是否出动画 */ - bindEvents () { - let element = this.element - let sessionKey = this.sessionKey - let sections = this.sections - let aniTimeAttr = element.getAttribute('animatetime') - let aniTime = isNaN(aniTimeAttr) ? 0.24 : Math.min(parseFloat(aniTimeAttr, 10), 1) - - sections.map(section => section.children.item(0)).forEach(accordionHeader => { - accordionHeader.addEventListener('click', function () { - let targetId = accordionHeader.getAttribute(ARIA_CONTROLS_ATTRIBUTE) - let targetContent = element.querySelector('#' + targetId) - let expanded = targetContent.getAttribute(ARIA_EXPANDED_ATTRIBUTE) - - let section = accordionHeader.parentNode - let showMore = section.querySelector('.show-more') - let showLess = section.querySelector('.show-less') - - if (expanded === OPEN_STATUS) { - // 收起内容区域 - heightAni({ - ele: targetContent, - type: 'fold', - transitionTime: aniTime, - cbFun: function () { - targetContent.setAttribute(ARIA_EXPANDED_ATTRIBUTE, CLOSE_STATUS) - }// .bind(undefined, targetContent) - }) + fold ({ + section, + targetId, + animate = true + }) { + section.removeAttribute(EXPANDED_ATTRIBUTE) + + let content = this.getContent(section) + if (!content) { + return + } - section.removeAttribute(EXPANDED_ATTRIBUTE) - if (showMore && showLess) { - util.css(showMore, 'display', 'block') - util.css(showLess, 'display', 'none') - } - - // sections.forEach(section => { - // let showMore = section.querySelector('.show-more') - // let showLess = section.querySelector('.show-less') - // section.classList.remove(EXPANDED_ATTRIBUTE) - // if (showMore && showLess) { - // util.css(showMore, 'display', 'block') - // util.css(showLess, 'display', 'none') - // } - // }) - - setSession(sessionKey, targetId, false) - } else { - // 同时只能展开一个节点 - if (element.hasAttribute('expaned-limit')) { - sections.forEach(section => { - let content = section.querySelector(`.${MIP_ACCORDION_CONTENT_CLASS}`) - let header = section.querySelector(`.${MIP_ACCORDION_HEADER_CLASS}`) - let id = header.getAttribute(ARIA_CONTROLS_ATTRIBUTE) - - section.removeAttribute(EXPANDED_ATTRIBUTE) - content.removeAttribute(ARIA_EXPANDED_ATTRIBUTE) - setSession(sessionKey, id, false) - - heightAni({ - ele: content, - type: 'fold', - transitionTime: aniTime, - cb: function () { - util.css(content, 'height', '') - } - }) - }) - } - - targetContent.setAttribute(ARIA_EXPANDED_ATTRIBUTE, OPEN_STATUS) - section.setAttribute(EXPANDED_ATTRIBUTE, OPEN_STATUS) - if (showMore && showLess) { - util.css(showLess, 'display', 'block') - util.css(showMore, 'display', 'none') - } - - // sections.forEach(section => { - // let showMore = section.querySelector('.show-more') - // let showLess = section.querySelector('.show-less') - // section.setAttribute(EXPANDED_ATTRIBUTE, OPEN_STATUS) - // if (showMore && showLess) { - // util.css(showLess, 'display', 'block') - // util.css(showMore, 'display', 'none') - // } - // }) - - // unfold animation - heightAni({ - ele: targetContent, - type: 'unfold', - oriHeight: 0, - transitionTime: aniTime, - cb: function () { - util.css(targetContent, 'height', '') - } - }) + // 记录折叠情况 + if (this.type === 'automatic') { + targetId = targetId || content.getAttribute('id') + setSession(this.sessionKey, targetId, false) + } - setSession(sessionKey, targetId, true) + // 折叠过程是否出动画效果 + if (animate) { + // 收起内容区域 + heightAni({ + ele: content, + type: 'fold', + transitionTime: this.aniTime, + cb () { + content.removeAttribute(ARIA_EXPANDED_ATTRIBUTE) } }) - }) + } else { + content.removeAttribute(ARIA_EXPANDED_ATTRIBUTE) + } } } diff --git a/components/mip-accordion/mip-accordion.less b/components/mip-accordion/mip-accordion.less index ad505f7f..ba9e55e0 100644 --- a/components/mip-accordion/mip-accordion.less +++ b/components/mip-accordion/mip-accordion.less @@ -2,25 +2,35 @@ mip-accordion { .mip-accordion-content { display: none !important; overflow: hidden; - } - section .show-more { - display: block; + &[aria-expanded=open] { + display: block !important; + } } - section[expanded=open] .show-more { - display: none !important; - } + section { + .show-more { + display: block; + } - .mip-accordion-content[aria-expanded="open"] { - display: block !important; - } + .show-less { + display: none; + } - section .show-less { - display: none; - } + &[expanded=open] { + > :first-child { + .show-more { + display: none !important; + } + + .show-less { + display: block !important; + } + } - section[expanded=open] .show-less { - display: block; + > :nth-child(2) { + display: block !important; + } + } } }