Skip to content

Commit

Permalink
new selectors (#642)
Browse files Browse the repository at this point in the history
* first pass of new selectors

* add padding for inline forms

* remove padding, update selectors for mobile

* fixed document overflow issue

* select prev/next

* updated tests

* added kiln hidden attribute

* small fix

* small fixes for edge cases, consistent unselection

* esc unselects
  • Loading branch information
nelsonpecora committed Sep 29, 2016
1 parent cd1de88 commit 18b8d85
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 303 deletions.
20 changes: 20 additions & 0 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ var nodeUrl = require('url'),
dom = require('@nymag/dom'),
EditorToolbar = require('./controllers/kiln-toolbar'),
render = require('./services/components/render'),
keyCode = require('keycode'),
select = require('./services/components/select'),
focus = require('./decorators/focus'),
progress = require('./services/progress'),
Konami = require('konami-js'),
takeOffEveryZig = require('./services/pane/move-zig'),
Expand Down Expand Up @@ -84,3 +87,20 @@ window.addEventListener('offline', function () {
});

new Konami(takeOffEveryZig);

// navigate components when hitting ↑ / ↓ arrows (if there's a component selected)
document.addEventListener('keydown', function (e) {
const current = select.getCurrentSelected();

if (current && !focus.hasCurrentFocus()) {
let key = keyCode(e);

if (key === 'up') {
e.preventDefault();
return select.navigateComponents(current, 'prev')(e);
} else if (key === 'down') {
e.preventDefault();
return select.navigateComponents(current, 'next')(e);
}
}
});
15 changes: 9 additions & 6 deletions controllers/kiln-toolbar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const dom = require('@nymag/dom'),
keyCode = require('keycode'),
events = require('../services/events'),
focus = require('../decorators/focus'),
select = require('../services/components/select'),
state = require('../services/page-state'),
progress = require('../services/progress'),
rules = require('../validators'),
Expand Down Expand Up @@ -53,13 +55,14 @@ EditorToolbar = function (el) {
}
});

// close ANY open forms if user hits ESC
// when the user hits ESC,
// if a form is open, close it
// else if a component is selected, unselect it (don't close form AND unselect)
document.addEventListener('keydown', function (e) {
if (e.keyCode === 27) {
focus.unfocus();
// note: this will work even if there isn't a current form open
// fun fact: it'll unselect components as well, in case the user has a form closed
// but a component selected, and they don't want that
var key = keyCode(e);

if (key === 'esc') {
return focus.hasCurrentFocus() ? focus.unfocus() : select.unselect();
}
});

Expand Down
1 change: 0 additions & 1 deletion decorators/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ function hasCurrentFocus() {
*/
function unfocus() {
if (forms.isFormValid()) {
select.unselect();
currentFocus = null;
return forms.close();
} else {
Expand Down
1 change: 1 addition & 0 deletions media/down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion media/parent.svg

This file was deleted.

1 change: 1 addition & 0 deletions media/up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion services/components/add-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ function addComponent(pane, field, name, prevRef) {
} else {
return render.addComponentsHandlers(newEl).then(function () {
focus.unfocus();
select.unselect();
progress.done();
return select.select(newEl);
});
Expand Down
182 changes: 113 additions & 69 deletions services/components/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ var _ = require('lodash'),
label = require('../label'),
scrollToY = require('../scroll').toY,
addComponentHandler = require('./add-component-handler'),
visibleComponents = require('./visible-components'),
hidden = 'kiln-hide',
selectorHeight = 56, // selector menus are 48px tall, offset is 8px
currentSelected;

/**
Expand Down Expand Up @@ -55,57 +57,84 @@ function getParentField(componentEl, parentSchema, property) {
return path && parentSchema[path] && parentSchema[path][property] && path;
}

/**
* get the top and bottom offset of an element
* @param {Element} el
* @returns {object}
*/
function getOffset(el) {
var rect = el.getBoundingClientRect(),
body = document.body,
doc = document.documentElement,
bodyHeight = body.getBoundingClientRect().height,
scrollTop = window.pageYOffset || doc.scrollTop || body.scrollTop,
clientTop = doc.clientTop || body.clientTop || 0,
top = rect.top + scrollTop - clientTop,
bottom = bodyHeight - (rect.bottom + scrollTop - clientTop);

return { top: Math.round(top), bottom: Math.round(bottom) };
}

/**
* add padding to top/bottom of document to account for selector menu overflow
* note: this assumes document.body has NO padding normally
* @param {Element} el that was selected
*/
function addPadding(el) {
var offset = getOffset(el);

// check top overflow
if (offset.top < selectorHeight) {
document.body.style['padding-top'] = `${selectorHeight - offset.top}px`;
}

if (offset.bottom < selectorHeight) {
document.body.style['padding-bottom'] = `${selectorHeight - offset.bottom}px`;
}
}

/**
* remove top/bottom padding from body
*/
function removePadding() {
document.body.style['padding-top'] = '0px';
document.body.style['padding-bottom'] = '0px';
}

/**
* set selection on a component
* @param {Element} el editable element or component el
* @param {{ref: string, path: string, data: object}} options
* @param {MouseEvent} e
*/
function select(el) {
var component = getComponentEl(el),
parent = getParentEl(component);
var component = getComponentEl(el);

// only one component can be selected at a time
unselect();

// selected component gets .selected, parent gets .selected-parent
if (component) {
if (component && component.tagName !== 'HTML') {
component.classList.add('selected');
addPadding(component);
currentSelected = component;
}
if (parent) {
parent.classList.add('selected-parent');
}
window.kiln.trigger('select', component);
}

/**
* remove selected classes on current and parent component
* @param {Element} [el]
* @param {Element} [parent]
*/
function removeClasses(el, parent) {
if (el) {
el.classList.remove('selected');
}
if (parent) {
parent.classList.remove('selected-parent');
}
window.kiln.trigger('select', component);
}

/**
* remove selection
*/
function unselect() {
var current, parent;
var current = currentSelected || dom.find('.component-selector-wrapper.selected');

if (currentSelected) {
current = currentSelected;
parent = dom.closest(currentSelected.parentNode, '[' + references.referenceAttribute + ']');
} else {
current = dom.find('.selected');
parent = dom.find('.selected-parent');
if (current) {
current.classList.remove('selected');
removePadding();
window.kiln.trigger('unselect', current);
}

removeClasses(current, parent);
window.kiln.trigger('unselect', current);
currentSelected = null;
}

Expand All @@ -131,7 +160,6 @@ function componentClickHandler(el, e) {
e.stopSelection = true;

if (!el.classList.contains('selected')) {
unselect();
select(el);
}
}
Expand Down Expand Up @@ -229,29 +257,6 @@ function addLabel(selector, name) {
labelEl.setAttribute('title', label(name));
}

/**
* add parent arrow and handler if parent exists
* @param {Element} selector
* @param {object} parent
*/
function addParentHandler(selector, parent) {
var button = dom.find(selector, '.selected-info-parent');

if (parent.el) {
// if parent exists at all, add the handler
button.classList.remove(hidden);
button.addEventListener('click', function (e) {
e.stopPropagation();
// Select the parent.
return focus.unfocus().then(function () {
unselect();
select(parent.el);
scrollToComponent(parent.el);
}).catch(_.noop);
});
}
}

/**
* determine if a component has settings
* @param {object} options
Expand Down Expand Up @@ -324,17 +329,6 @@ function addDeleteHandler(selector, parent, el, options) {
}
}

/**
* unhide bottom menu if add component is available
* @param {Element} selector
* @param {object} parent
*/
function unhideBottomMenu(selector, parent) {
if (parent.isComponentList || parent.isComponentProp) {
dom.find(selector, '.component-selector-bottom').classList.remove(hidden);
}
}

/**
* unhide and add handler for add component (to list)
* @param {Element} selector
Expand Down Expand Up @@ -371,6 +365,43 @@ function addReplaceHandler(selector, parent, options) {
}
}

/**
* navigate to prev/next visible component
* @param {Element} el of current component
* @param {string} direction ('prev' or 'next')
* @returns {Function}
*/
function navigateComponents(el, direction) {
return function (e) {
var component = direction === 'prev' ? visibleComponents.getPrev(el) : visibleComponents.getNext(el);

e.stopPropagation();

if (component) {
return focus.unfocus().then(function () {
select(component);
scrollToComponent(component);
}).catch(_.noop);
}
};
}

/**
* navigate to prev/next (and parent/child) components
* @param {Element} selector
* @param {Element} el
*/
function addNavHandler(selector, el) {
var prevButton = dom.find(selector, '.selector-nav-up'),
nextButton = dom.find(selector, '.selector-nav-down');

prevButton.addEventListener('click', navigateComponents(el, 'prev'));
nextButton.addEventListener('click', navigateComponents(el, 'next'));

// note: client.js sets up a global keyboard handler that also
// calls navigateComponents on ↑ / ↓ key press
}

/**
* add drag within a component list.
* @param {Element} el (component element, not the selector)
Expand Down Expand Up @@ -400,22 +431,20 @@ function handler(el, options) {
// add options to the component selector
// set component label
addLabel(selector, name);
// if parent, unhide + add handler
addParentHandler(selector, parent);

// if settings, unhide + add handler
addSettingsHandler(selector, options);
// if delete, unhide + add handler
addDeleteHandler(selector, parent, el, options);

// if component lives in a list or property, unhide bottom
// note: more options might exist in the bottom menu in the future
unhideBottomMenu(selector, parent);
// if list, unhide + add handler
addAddHandler(selector, parent, options);
// if property, unhide + add handler
addReplaceHandler(selector, parent, options);

// add prev/next navigation handlers
addNavHandler(selector, el);

// if drag, add class
// note: this adds a class to the component itself,
// not the selector
Expand All @@ -438,11 +467,26 @@ function handler(el, options) {
});
}

/**
* see if there's a currently selected component,
* useful for keyboard component navigation
* @returns {Element|undefined|null}
*/
function getCurrentSelected() {
return currentSelected;
}

// select and unselect
module.exports.select = select;
module.exports.unselect = unselect;
module.exports.scrollToComponent = scrollToComponent;

// get current selected component
module.exports.getCurrentSelected = getCurrentSelected;

// navigate to prev/next component
module.exports.navigateComponents = navigateComponents;

// decorators
module.exports.when = when;
module.exports.handler = handler;
Expand Down
Loading

0 comments on commit 18b8d85

Please sign in to comment.