diff --git a/src/js/components/controls/index.js b/src/js/components/controls/index.js index 29866159..2f7b0075 100644 --- a/src/js/components/controls/index.js +++ b/src/js/components/controls/index.js @@ -25,15 +25,14 @@ const defaultElements = [...formControls, ...htmlControls, ...layoutControls] */ export class Controls { constructor() { - const _this = this this.data = new Map() this.controlEvents = { focus: ({ target }) => { const group = target.closest(`.${CONTROL_GROUP_CLASSNAME}`) - return group && _this.panels.nav.refresh(indexOfNode(group)) + return group && this.panels.nav.refresh(indexOfNode(group)) }, - click: ({ target }) => _this.addElement(target.parentElement.id), + click: ({ target }) => this.addElement(target.parentElement.id), } } @@ -249,14 +248,14 @@ export class Controls { * @return {DOM} */ buildDOM(sticky) { - const _this = this const groupedFields = this.groupElements() const formActions = this.formActions() - _this.panels = new Panels({ panels: groupedFields, type: 'controls', displayType: 'slider' }) + const { displayType } = this.options.panels + this.panels = new Panels({ panels: groupedFields, type: 'controls', displayType }) const groupsWrapClasses = ['control-groups', 'formeo-panels-wrap', `panel-count-${groupedFields.length}`] const groupsWrap = dom.create({ className: groupsWrapClasses, - content: _this.panels.children, + content: [this.panels.panelNav, this.panels.panelsWrap], }) let controlClass = 'formeo-controls' @@ -303,7 +302,7 @@ export class Controls { filteredTerm.remove() } }, - addElement: _this.addElement, + addElement: this.addElement, // @todo finish the addGroup method addGroup: group => console.log(group), } @@ -324,7 +323,7 @@ export class Controls { pull: 'clone', put: false, }, - onRemove: _this.applyControlEvents, + onRemove: this.applyControlEvents, onStart: ({ item }) => { const { controlData } = this.get(item.id) if (this.options.ghostPreview) { diff --git a/src/js/components/controls/options.js b/src/js/components/controls/options.js index 9ce710cf..7e81b266 100644 --- a/src/js/components/controls/options.js +++ b/src/js/components/controls/options.js @@ -26,6 +26,7 @@ const defaultOptions = Object.freeze({ }, elements: [], container: null, + panels: { displayType: 'slider' }, }) export default defaultOptions diff --git a/src/js/components/fields/field.js b/src/js/components/fields/field.js index 6eedb47b..478a4dde 100644 --- a/src/js/components/fields/field.js +++ b/src/js/components/fields/field.js @@ -216,19 +216,20 @@ export default class Field extends Component { const panelsData = { panels: this.editPanels.map(({ panelConfig }) => panelConfig), id: _this.id, + displayType: 'auto', } const editPanelLength = this.editPanels.length if (editPanelLength) { - const editPanels = (_this.panels = new Panels(panelsData)) + this.panels = new Panels(panelsData) fieldEdit.className.push(`panel-count-${editPanelLength}`) - fieldEdit.content = editPanels.children - _this.panelNav = editPanels.nav - _this.resizePanelWrap = editPanels.action.resize + fieldEdit.content = [this.panels.panelNav, this.panels.panelsWrap] + this.panelNav = this.panels.nav + this.resizePanelWrap = this.panels.nav.refresh fieldEdit.action = { onRender: () => { - _this.resizePanelWrap() + this.resizePanelWrap() if (!editPanelLength) { const field = this.dom const editToggle = field.querySelector('.item-edit-toggle') diff --git a/src/js/components/panels.js b/src/js/components/panels.js index 82143aed..329ff866 100644 --- a/src/js/components/panels.js +++ b/src/js/components/panels.js @@ -3,12 +3,14 @@ import Sortable from 'sortablejs' import h, { indexOfNode } from '../common/helpers' import dom from '../common/dom' import { ANIMATION_SPEED_SLOW, ANIMATION_SPEED_FAST } from '../constants' +import { merge } from '../common/utils' -const defaults = { +const defaults = Object.freeze({ type: 'field', -} + displayType: 'slider', +}) -const getTransition = val => ({ transform: `translateX(${val}px)` }) +const getTransition = val => ({ transform: `translateX(${val ? `${val}px` : 0})` }) /** * Edit and control sliding panels @@ -20,50 +22,45 @@ export default class Panels { * @return {Object} Panels */ constructor(options) { - this.opts = Object.assign({}, defaults, options) - - this.labels = this.panelNav() - const panelsWrap = this.createPanelsWrap() + this.opts = merge(defaults, options) + this.panelDisplay = this.opts.displayType - this.panels = panelsWrap.childNodes this.activePanelIndex = 0 - this.currentPanel = this.panels[this.activePanelIndex] - this.nav = this.navActions() - this.panelDisplay = this.opts.displayType || 'slider' + this.panelNav = this.createPanelNav() + const panelsWrap = this.createPanelsWrap() + this.nav = this.navActions() const resizeObserver = new window.ResizeObserver(([{ contentRect: { width } }]) => { - if (this.activePanelIndex && this.currentWidth !== width) { + if (this.currentWidth !== width) { + this.toggleTabbedLayout() this.currentWidth = width this.nav.setTranslateX(this.activePanelIndex, false) - this.toggleTabbedLayout() } }) const observeTimeout = window.setTimeout(() => { - resizeObserver.observe(this.panelsWrap) + resizeObserver.observe(panelsWrap) window.clearTimeout(observeTimeout) }, ANIMATION_SPEED_SLOW) - - return { - children: [this.labels, panelsWrap], - nav: this.nav, - action: { - resize: this.resizePanels, - }, - } } - toggleTabbedLayout = () => { - const panelsWrap = this.panelsWrap - const column = panelsWrap.parentElement.parentElement + getPanelDisplay() { + const column = this.panelsWrap const width = parseInt(dom.getStyle(column, 'width')) const autoDisplayType = width > 390 ? 'tabbed' : 'slider' - this.panelDisplay = this.opts.displayType || autoDisplayType - const isTabbed = this.panelDisplay === 'tabbed' - panelsWrap.parentElement.classList.toggle('tabbed-panels', isTabbed) + const isAuto = this.opts.displayType === 'auto' + this.panelDisplay = isAuto ? autoDisplayType : this.opts.displayType || defaults.displayType + console.log(autoDisplayType, this.panelDisplay) + return this.panelDisplay + } + + toggleTabbedLayout = () => { + this.getPanelDisplay() + const isTabbed = this.isTabbed + this.panelsWrap.parentElement.classList.toggle('tabbed-panels', isTabbed) if (isTabbed) { - column.querySelector('.panel-labels div').removeAttribute('style') + this.panelNav.removeAttribute('style') } return isTabbed } @@ -86,18 +83,20 @@ export default class Panels { * @return {Object} DOM element */ createPanelsWrap() { - this.panelsWrap = dom.create({ + const panelsWrap = dom.create({ className: 'panels', - content: this.opts.panels, + content: this.opts.panels.map(({ config: { label }, ...panel }) => panel), }) - this.panelsWrap = this.panelsWrap - if (this.opts.type === 'field') { - this.sortableProperties(this.panelsWrap) + this.sortableProperties(panelsWrap) } - return this.panelsWrap + this.panelsWrap = panelsWrap + this.panels = panelsWrap.children + this.currentPanel = this.panels[this.activePanelIndex] + + return panelsWrap } /** @@ -130,43 +129,41 @@ export default class Panels { }) } - /** - * Panel navigation, tabs and arrow buttons for slider - * @return {Object} DOM object for panel navigation wrapper - */ - panelNav() { - const _this = this - const panelNavLabels = { + createPanelNavLabels() { + const labels = this.opts.panels.map(panel => ({ + tag: 'h5', + action: { + click: evt => { + const index = indexOfNode(evt.target, evt.target.parentElement) + this.currentPanel = this.panels[index] + const labels = evt.target.parentElement.childNodes + this.nav.refresh(index) + dom.removeClasses(labels, 'active-tab') + evt.target.classList.add('active-tab') + }, + }, + content: panel.config.label, + })) + + const panelLabels = { className: 'panel-labels', content: { - content: [], + content: labels, }, } - const panels = this.opts.panels // make new array - - for (let i = 0; i < panels.length; i++) { - const panelLabel = { - tag: 'h5', - action: { - click: evt => { - const index = indexOfNode(evt.target, evt.target.parentElement) - _this.currentPanel = _this.panels[index] - const labels = evt.target.parentElement.childNodes - _this.nav.refresh(index) - dom.removeClasses(labels, 'active-tab') - evt.target.classList.add('active-tab') - }, - }, - content: panels[i].config.label, - } - delete panels[i].config.label - if (i === 0) { - panelLabel.className = 'active-tab' - } + const [firstLabel] = labels + firstLabel.className = 'active-tab' - panelNavLabels.content.content.push(panelLabel) - } + return dom.create(panelLabels) + } + + /** + * Panel navigation, tabs and arrow buttons for slider + * @return {Object} DOM object for panel navigation wrapper + */ + createPanelNav() { + this.labels = this.createPanelNavLabels() const next = { tag: 'button', @@ -206,27 +203,30 @@ export default class Panels { attrs: { className: 'panel-nav', }, - content: [prev, panelNavLabels, next], + content: [prev, this.labels, next], }) } + get isTabbed() { + return this.panelDisplay === 'tabbed' + } + /** * Handlers for navigating between panel groups * @todo refactor to use requestAnimationFrame instead of css transitions * @return {Object} actions that control panel groups */ navActions() { - const _this = this const action = {} const groupParent = this.currentPanel.parentElement - const firstControlNav = this.labels.querySelector('.panel-labels').firstChild + const labelWrap = this.labels.firstChild const siblingGroups = this.currentPanel.parentElement.childNodes this.activePanelIndex = indexOfNode(this.currentPanel, groupParent) let offset = { nav: 0, panel: 0 } let lastOffset = { ...offset } const groupChange = newIndex => { - const labels = this.labels.querySelector('.panel-labels div').children + const labels = labelWrap.children dom.removeClasses(siblingGroups, 'active-panel') dom.removeClasses(labels, 'active-tab') this.currentPanel = siblingGroups[newIndex] @@ -238,21 +238,14 @@ export default class Panels { const getOffset = index => { return { - nav: -firstControlNav.offsetWidth * index, + nav: -labelWrap.offsetWidth * index, panel: -groupParent.offsetWidth * index, } } - const translateX = ({ offset, reset, duration = ANIMATION_SPEED_FAST, animate = true }) => { - if (_this.panelDisplay === 'tabbed') { - firstControlNav.removeAttribute('style') - const { transform } = getTransition(offset.panel) - groupParent.style.transform = transform - return null - } - + const translateX = ({ offset, reset, duration = ANIMATION_SPEED_FAST, animate = !this.isTabbed }) => { const panelQueue = [getTransition(lastOffset.panel), getTransition(offset.panel)] - const navQueue = [getTransition(lastOffset.nav), getTransition(offset.nav)] + const navQueue = [getTransition(lastOffset.nav), getTransition(this.isTabbed ? 0 : offset.nav)] if (reset) { const [panelStart] = panelQueue @@ -268,7 +261,8 @@ export default class Panels { } const panelTransition = groupParent.animate(panelQueue, animationOptions) - firstControlNav.animate(navQueue, animationOptions) + labelWrap.animate(navQueue, animationOptions) + const handleFinish = () => { this.panelsWrap.style.height = dom.getStyle(this.currentPanel, 'height') panelTransition.removeEventListener('finish', handleFinish) @@ -289,8 +283,8 @@ export default class Panels { this.activePanelIndex = newIndex groupChange(newIndex) } - _this.resizePanels() - action.setTranslateX(this.activePanelIndex) + this.resizePanels() + action.setTranslateX(this.activePanelIndex, false) } /** @@ -302,7 +296,7 @@ export default class Panels { if (newIndex !== siblingGroups.length) { const curPanel = groupChange(newIndex) offset = { - nav: -firstControlNav.offsetWidth * newIndex, + nav: -labelWrap.offsetWidth * newIndex, panel: -curPanel.offsetLeft, } translateX({ offset }) @@ -323,7 +317,7 @@ export default class Panels { const newIndex = this.activePanelIndex - 1 const curPanel = groupChange(newIndex) offset = { - nav: -firstControlNav.offsetWidth * newIndex, + nav: -labelWrap.offsetWidth * newIndex, panel: -curPanel.offsetLeft, } translateX({ offset }) diff --git a/src/sass/components/_controls.scss b/src/sass/components/_controls.scss index 3159eaab..dd3773e4 100644 --- a/src/sass/components/_controls.scss +++ b/src/sass/components/_controls.scss @@ -23,6 +23,12 @@ width: calc(100% - 2px); } + .tabbed-panels { + nav { + padding: 0; + } + } + nav { position: relative; padding: 0 $action-btn-width; diff --git a/src/sass/components/_field-edit.scss b/src/sass/components/_field-edit.scss index 87aef32b..0cc0ecfb 100644 --- a/src/sass/components/_field-edit.scss +++ b/src/sass/components/_field-edit.scss @@ -10,8 +10,6 @@ margin-bottom: 0; padding: 0; overflow: hidden; - border-top-left-radius: $input-border-radius; - border-top-right-radius: $input-border-radius; button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; diff --git a/src/sass/components/_panels.scss b/src/sass/components/_panels.scss index aecfd566..d556625f 100644 --- a/src/sass/components/_panels.scss +++ b/src/sass/components/_panels.scss @@ -83,10 +83,9 @@ background: $white; overflow: hidden; text-align: center; - - div { - white-space: nowrap; - } + white-space: nowrap; + border-top-left-radius: $input-border-radius; + border-top-right-radius: $input-border-radius; } } @@ -99,10 +98,6 @@ } } - .panels { - transition: none; - } - .f-panel { background-color: $white; } @@ -132,8 +127,3 @@ } } } - -.no-transition .panels, -.no-transition .panel-labels div { - transition: none; -}