Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

angular-stackables

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

angular-stackables - npm Package Compare versions

Comparing version 0.0.1 to 0.0.5

8

bower.json
{
"name": "angular-stackables",
"version": "0.0.1",
"version": "0.0.5",
"description": "AngularJS stackable widgets built on HTML5 dialog.",

@@ -13,3 +13,7 @@ "authors": [

"dialog-polyfill": "0.2.0"
}
},
"ignore": [
"node_modules",
"bower_components"
]
}
{
"name": "angular-stackables",
"version": "0.0.1",
"version": "0.0.5",
"dependencies": {}
}

@@ -5,1 +5,96 @@ angular-stackables

AngularJS stackable widgets (modals, popovers, menus) that use HTML5 dialog
# Examples
## A simple modal
```html
<div ng-controller="TestController as test">
<div stackable="test.isOpen"
stackable-modal="true"
stackable-disable-escape="false"
stackable-closing="test.modalClosing(err, result)"
stackable-closed="test.modalClosed(err, result)">
<div class="stackable-dialog">
<div inner-directive>
<p>Test Dialog</p>
</div>
</div>
</div>
</div>
```
```js
function TestController() {
this.isOpen = false;
this.modalClosing = function(err, result) {
/* return false or a Promise that resolves to false to prevent close */
return true;
};
this.modalClosed = function(err, result) {
console.log('modal closed', err, result);
};
}
module.directive({
innerDirective: function() {
return {
restrict: 'A',
require: '^stackable',
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
// use stackable controller API to close stackable programmatically
ctrl.close(null, 'closed from inner directive');
}
};
}
});
```
## A simple popover
```html
<div ng-controller="TestController as test">
<a class="btn" stackable-trigger="test.popoverState">
<i class="caret"></i>
</a>
<div stackable-popover="test.popoverState"
stackable-placement="bottom"
stackable-alignment="center"
stackable-enable-escape="true">
<h3 class="stackable-popover-title">Title</h3>
<div class="stackable-popover-body">
<p>Hello World</p>
</div>
</div>
</div>
```
## A simple menu
```html
<div ng-controller="TestController as test">
<a class="btn" stackable-trigger="test.menuState">
<i class="caret"></i>
</a>
<div stackable-popover="test.menuState"
stackable-hide-arrow="true"
stackable-placement="bottom"
stackable-alignment="right"
stackable-enable-escape="true">
<div class="stackable-popover-body">
<ul class="unstyled">
<li>
<a href="#">Menu Item</a>
</li>
</ul>
</div>
</div>
</div>
```

@@ -14,5 +14,8 @@ /*!

module.directive({'stackable': factory});
module.directive({stackable: stackableDirective});
module.directive({stackableCancel: stackableCancelDirective});
module.directive({stackablePopover: stackablePopoverDirective});
module.directive({stackableTrigger: ['$parse', stackableTriggerDirective]});
function factory() {
function stackableDirective() {
return {

@@ -30,5 +33,6 @@ scope: {

template: ' \
<dialog class="stackable"> \
<div data-ng-if="show" class="stackable-content"> \
<div data-ng-transclude></div> \
<dialog class="stackable" ng-class="{\'stackable-modal\': modal}" \
ng-show="show"> \
<div ng-if="show" class="stackable-content"> \
<div ng-transclude></div> \
</div> \

@@ -80,3 +84,4 @@ </dialog>',

dialog.addEventListener('close', function() {
dialog.addEventListener('close', function(e) {
e.stopPropagation();
scope.show = open = false;

@@ -123,4 +128,333 @@ var count = body.data('stackables') - 1;

return {stackable: factory};
function stackableDirective() {
return {
scope: {
show: '=stackable',
modal: '=?stackableModal',
disableEscape: '=?stackableDisableEscape',
closing: '&?stackableClosing',
closed: '&?stackableClosed'
},
restrict: 'A',
replace: true,
transclude: true,
template: ' \
<dialog class="stackable" ng-class="{\'stackable-modal\': modal}" \
ng-show="show"> \
<div ng-if="show" class="stackable-content"> \
<div ng-transclude></div> \
</div> \
</dialog>',
controller: ['$scope', Controller],
link: Link
};
function Controller($scope) {
var self = this;
var stackable = $scope.stackable = self;
// close the stackable unless 'closing' callback aborts
self.close = function(err, result) {
var closing = $scope.closing || angular.noop;
var shouldClose = closing.call($scope.$parent, {
err: err,
result: result
});
Promise.resolve(shouldClose).then(function() {
if(shouldClose !== false) {
stackable.error = err;
stackable.result = result;
$scope.show = false;
$scope.$apply();
}
});
};
}
function Link(scope, element) {
var open = false;
var body = angular.element('body');
var dialog = element[0];
// use polyfill if necessary
if(!dialog.showModal && typeof dialogPolyfill !== 'undefined') {
dialogPolyfill.registerDialog(dialog);
}
dialog.addEventListener('cancel', function(e) {
if(!!scope.disableEscape) {
e.preventDefault();
} else {
scope.stackable.error = 'canceled';
scope.stackable.result = null;
}
});
dialog.addEventListener('close', function(e) {
e.stopPropagation();
scope.show = open = false;
var count = body.data('stackables') - 1;
body.data('stackables', count);
if(count === 0) {
body.removeClass('stackable-modal-open');
}
scope.$apply();
if(scope.closed) {
scope.closed.call(scope.$parent, {
err: scope.stackable.error,
result: scope.stackable.result
});
}
});
scope.$watch('show', function(value) {
if(value) {
if(!open) {
if(!!scope.modal) {
dialog.showModal();
body.addClass('stackable-modal-open');
} else {
dialog.show();
}
open = true;
scope.stackable.error = scope.stackable.result = undefined;
var count = body.data('stackables') || 0;
body.data('stackables', count + 1);
}
} else if(open) {
// schedule dialog close to avoid $digest already in progress
// as 'close' event handler may be called from here or externally
setTimeout(function() {
dialog.close();
});
open = false;
}
});
}
}
function stackableCancelDirective() {
return {
restrict: 'AC',
require: '^stackable',
link: function(scope, element, attrs, ctrl) {
element.on('click', function() {
ctrl.close('canceled', null);
});
}
};
}
function stackablePopoverDirective() {
return {
scope: {
state: '=stackablePopover',
hideArrow: '=?stackableHideArrow',
alignment: '@?stackableAlignment',
placement: '@?stackablePlacement',
enableEscape: '=?stackableEnableEscape',
disableBlurClose: '=?stackableDisableBlurClose'
},
restrict: 'A',
replace: true,
transclude: true,
template: ' \
<div class="stackable-popover" ng-class="{ \
\'stackable-place-top\': !placement || placement == \'top\', \
\'stackable-place-right\': placement == \'right\', \
\'stackable-place-bottom\': placement == \'bottom\', \
\'stackable-place-left\': placement == \'left\', \
\'stackable-align-center\': !alignment || alignment == \'center\', \
\'stackable-align-top\': alignment == \'top\', \
\'stackable-align-right\': alignment == \'right\', \
\'stackable-align-bottom\': alignment == \'bottom\', \
\'stackable-align-left\': alignment == \'left\', \
\'stackable-no-arrow\': hideArrow}"> \
<div stackable="state.show"> \
<div class="stackable-popover-content"> \
<div ng-if="!hideArrow" class="stackable-arrow"></div> \
<div ng-transclude></div> \
</div> \
</div> \
</div>',
compile: Compile
};
function Compile(tElement, tAttrs, transcludeFn) {
var extents = {};
return function(scope, element) {
// measure popover content
transcludeFn(scope.$parent, function(clone) {
var content = angular.element(' \
<div class="stackable-popover-content" style="width:auto"> \
<div ng-if="!hideArrow" class="stackable-arrow"></div> \
</div>');
content.append(clone);
content.css({display: 'none'});
angular.element('body').append(content);
extents.height = content.outerHeight(true);
extents.width = content.outerWidth(true);
// setTimeout hack to ensure content size has settled on chrome
setTimeout(function() {
extents.height = content.outerHeight(true);
extents.width = content.outerWidth(true);
content.remove();
});
});
// whenever state changes, reposition popover
scope.$watch('state', function() {
setTimeout(reposition);
}, true);
// close when pressing escape anywhere or clicking away
angular.element(document)
.on('keyup', closeOnEscape)
.on('click', closeOnClick);
scope.$on('$destroy', function() {
angular.element(document)
.off('keyup', closeOnEscape)
.off('click', closeOnClick);
});
function closeOnClick(e) {
// close if target is not the trigger and is not in the popover
var target = angular.element(e.target);
var trigger = target.data('stackable-state');
if(scope.state !== trigger && target.closest(element).length === 0) {
scope.state.show = false;
scope.$apply();
}
}
function closeOnEscape(e) {
if(scope.enableEscape && e.keyCode === 27) {
e.stopPropagation();
scope.state.show = false;
scope.$apply();
}
}
function reposition() {
// resize popover content
var content = element.find('.stackable-popover-content');
if(!content.length) {
return;
}
content.css(extents);
var height = content.outerHeight(true);
var width = content.outerWidth(true);
// position popover
var position = {top: 0, left: 0};
var alignment = scope.alignment || 'center';
var placement = scope.placement || 'top';
if(placement === 'top' || placement === 'bottom') {
// treat invalid 'top' or 'bottom' as 'center'
if(['center', 'top', 'bottom'].indexOf(alignment) !== -1) {
var triggerCenterX = (scope.state.position.left +
scope.state.position.width / 2);
position.left = triggerCenterX - width / 2;
} else if(alignment === 'left') {
position.left = scope.state.position.left;
} else {
// alignment 'right'
position.left = (scope.state.position.left +
scope.state.position.width - width);
}
position.top = scope.state.position.top;
if(placement === 'top') {
position.top -= height;
} else {
position.top += scope.state.position.height;
}
} else {
// else placement is 'left' or 'right'
// treat invalid 'left' or 'right' as 'center'
if(['center', 'left', 'right'].indexOf(alignment) !== -1) {
var triggerCenterY = (scope.state.position.top +
scope.state.position.height / 2);
position.top = triggerCenterY - height / 2;
} else if(alignment === 'top') {
position.top = scope.state.position.top;
} else {
// alignment 'bottom'
position.top = (scope.state.position.top +
scope.state.position.height - height);
}
position.left = scope.state.position.left;
if(placement === 'left') {
position.left -= width;
} else {
position.left += scope.state.position.width;
}
}
position.top += 'px';
position.left += 'px';
element.css(position);
}
};
}
}
function stackableTriggerDirective($parse) {
return {
restrict: 'A',
link: Link
};
function Link(scope, element, attrs) {
// track stackable state
var state;
initState(attrs.stackableTrigger);
attrs.$observe('stackableTrigger', function(value) {
initState(value);
});
// update element position when window resized
angular.element(window).resize(resized);
scope.$on('$destroy', function() {
angular.element(window).off('resize', resized);
});
// toggle show on click
element.on('click', function() {
scope.$apply(function() {
state.show = !state.show;
updateState(state);
});
});
function resized() {
updateState(state);
scope.$apply();
}
function initState(expr) {
var get = $parse(expr);
var set = get.assign || angular.noop;
state = get(scope) || {};
if(!('show' in state)) {
state.show = false;
}
updateState(state);
set(scope, state);
element.data('stackable-state', state);
}
function updateState(state) {
var offset = element.offset();
state.position = {
top: offset.top,
left: offset.left,
height: element.outerHeight(false),
width: element.outerWidth(false),
heightWithMargin: element.outerHeight(true),
widthWithMargin: element.outerWidth(true)
};
return state;
}
}
}
})();

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc