This commit is contained in:
2022-01-25 15:21:01 +06:00
parent 3746efc596
commit e4dfdc07fc
595 changed files with 59923 additions and 47 deletions

View File

@@ -0,0 +1,66 @@
import {getComponentName} from './component';
import {apply, fastdom, hasAttr, inBrowser} from 'uikit-util';
export default function (UIkit) {
const {connect, disconnect} = UIkit;
if (!inBrowser || !window.MutationObserver) {
return;
}
fastdom.read(function () {
if (document.body) {
apply(document.body, connect);
}
new MutationObserver(records =>
records.forEach(applyChildListMutation)
).observe(document, {
childList: true,
subtree: true
});
new MutationObserver(records =>
records.forEach(applyAttributeMutation)
).observe(document, {
attributes: true,
subtree: true
});
UIkit._initialized = true;
});
function applyChildListMutation({addedNodes, removedNodes}) {
for (let i = 0; i < addedNodes.length; i++) {
apply(addedNodes[i], connect);
}
for (let i = 0; i < removedNodes.length; i++) {
apply(removedNodes[i], disconnect);
}
}
function applyAttributeMutation({target, attributeName}) {
const name = getComponentName(attributeName);
if (!name || !(name in UIkit)) {
return;
}
if (hasAttr(target, attributeName)) {
UIkit[name](target);
return;
}
const component = UIkit.getComponent(target, name);
if (component) {
component.$destroy();
}
}
}

View File

@@ -0,0 +1,101 @@
import {$$, assign, camelize, fastdom, hyphenate, isPlainObject, memoize, startsWith} from 'uikit-util';
export default function (UIkit) {
const DATA = UIkit.data;
const components = {};
UIkit.component = function (name, options) {
const id = hyphenate(name);
name = camelize(id);
if (!options) {
if (isPlainObject(components[name])) {
components[name] = UIkit.extend(components[name]);
}
return components[name];
}
UIkit[name] = function (element, data) {
const component = UIkit.component(name);
return component.options.functional
? new component({data: isPlainObject(element) ? element : [...arguments]})
: !element ? init(element) : $$(element).map(init)[0];
function init(element) {
const instance = UIkit.getComponent(element, name);
if (instance) {
if (data) {
instance.$destroy();
} else {
return instance;
}
}
return new component({el: element, data});
}
};
const opt = isPlainObject(options) ? assign({}, options) : options.options;
opt.name = name;
if (opt.install) {
opt.install(UIkit, opt, name);
}
if (UIkit._initialized && !opt.functional) {
fastdom.read(() => UIkit[name](`[uk-${id}],[data-uk-${id}]`));
}
return components[name] = isPlainObject(options) ? opt : options;
};
UIkit.getComponents = element => element && element[DATA] || {};
UIkit.getComponent = (element, name) => UIkit.getComponents(element)[name];
UIkit.connect = node => {
if (node[DATA]) {
for (const name in node[DATA]) {
node[DATA][name]._callConnected();
}
}
for (let i = 0; i < node.attributes.length; i++) {
const name = getComponentName(node.attributes[i].name);
if (name && name in components) {
UIkit[name](node);
}
}
};
UIkit.disconnect = node => {
for (const name in node[DATA]) {
node[DATA][name]._callDisconnected();
}
};
}
export const getComponentName = memoize(attribute => {
return startsWith(attribute, 'uk-') || startsWith(attribute, 'data-uk-')
? camelize(attribute.replace('data-uk-', '').replace('uk-', ''))
: false;
});

View File

@@ -0,0 +1,78 @@
import {$, apply, isString, mergeOptions, parents, toNode} from 'uikit-util';
export default function (UIkit) {
const DATA = UIkit.data;
UIkit.use = function (plugin) {
if (plugin.installed) {
return;
}
plugin.call(null, this);
plugin.installed = true;
return this;
};
UIkit.mixin = function (mixin, component) {
component = (isString(component) ? UIkit.component(component) : component) || this;
component.options = mergeOptions(component.options, mixin);
};
UIkit.extend = function (options) {
options = options || {};
const Super = this;
const Sub = function UIkitComponent(options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.options = mergeOptions(Super.options, options);
Sub.super = Super;
Sub.extend = Super.extend;
return Sub;
};
UIkit.update = function (element, e) {
element = element ? toNode(element) : document.body;
parents(element).reverse().forEach(element => update(element[DATA], e));
apply(element, element => update(element[DATA], e));
};
let container;
Object.defineProperty(UIkit, 'container', {
get() {
return container || document.body;
},
set(element) {
container = $(element);
}
});
function update(data, e) {
if (!data) {
return;
}
for (const name in data) {
if (data[name]._connected) {
data[name]._callUpdate(e);
}
}
}
}

View File

@@ -0,0 +1,141 @@
import {assign, fastdom, hasOwn, isEqual, isPlainObject} from 'uikit-util';
export default function (UIkit) {
UIkit.prototype._callHook = function (hook) {
const handlers = this.$options[hook];
if (handlers) {
handlers.forEach(handler => handler.call(this));
}
};
UIkit.prototype._callConnected = function () {
if (this._connected) {
return;
}
this._data = {};
this._computeds = {};
this._initProps();
this._callHook('beforeConnect');
this._connected = true;
this._initEvents();
this._initObservers();
this._callHook('connected');
this._callUpdate();
};
UIkit.prototype._callDisconnected = function () {
if (!this._connected) {
return;
}
this._callHook('beforeDisconnect');
this._disconnectObservers();
this._unbindEvents();
this._callHook('disconnected');
this._connected = false;
delete this._watch;
};
UIkit.prototype._callUpdate = function (e = 'update') {
if (!this._connected) {
return;
}
if (e === 'update' || e === 'resize') {
this._callWatches();
}
if (!this.$options.update) {
return;
}
if (!this._updates) {
this._updates = new Set();
fastdom.read(() => {
if (this._connected) {
runUpdates.call(this, this._updates);
}
delete this._updates;
});
}
this._updates.add(e.type || e);
};
UIkit.prototype._callWatches = function () {
if (this._watch) {
return;
}
const initial = !hasOwn(this, '_watch');
this._watch = fastdom.read(() => {
if (this._connected) {
runWatches.call(this, initial);
}
this._watch = null;
});
};
function runUpdates(types) {
const updates = this.$options.update;
for (let i = 0; i < updates.length; i++) {
const {read, write, events} = updates[i];
if (!types.has('update') && (!events || !events.some(type => types.has(type)))) {
continue;
}
let result;
if (read) {
result = read.call(this, this._data, types);
if (result && isPlainObject(result)) {
assign(this._data, result);
}
}
if (write && result !== false) {
fastdom.write(() => write.call(this, this._data, types));
}
}
}
function runWatches(initial) {
const {$options: {computed}} = this;
const values = assign({}, this._computeds);
this._computeds = {};
for (const key in computed) {
const {watch, immediate} = computed[key];
if (watch && (
initial && immediate
|| hasOwn(values, key) && !isEqual(values[key], this[key])
)) {
watch.call(this, this[key], values[key]);
}
}
}
}

View File

@@ -0,0 +1,24 @@
import globalAPI from './global';
import hooksAPI from './hooks';
import stateAPI from './state';
import instanceAPI from './instance';
import componentAPI from './component';
import * as util from 'uikit-util';
const UIkit = function (options) {
this._init(options);
};
UIkit.util = util;
UIkit.data = '__uikit__';
UIkit.prefix = 'uk-';
UIkit.options = {};
UIkit.version = VERSION;
globalAPI(UIkit);
hooksAPI(UIkit);
stateAPI(UIkit);
componentAPI(UIkit);
instanceAPI(UIkit);
export default UIkit;

View File

@@ -0,0 +1,87 @@
import {hyphenate, isEmpty, memoize, remove, within} from 'uikit-util';
export default function (UIkit) {
const DATA = UIkit.data;
UIkit.prototype.$create = function (component, element, data) {
return UIkit[component](element, data);
};
UIkit.prototype.$mount = function (el) {
const {name} = this.$options;
if (!el[DATA]) {
el[DATA] = {};
}
if (el[DATA][name]) {
return;
}
el[DATA][name] = this;
this.$el = this.$options.el = this.$options.el || el;
if (within(el, document)) {
this._callConnected();
}
};
UIkit.prototype.$reset = function () {
this._callDisconnected();
this._callConnected();
};
UIkit.prototype.$destroy = function (removeEl = false) {
const {el, name} = this.$options;
if (el) {
this._callDisconnected();
}
this._callHook('destroy');
if (!el || !el[DATA]) {
return;
}
delete el[DATA][name];
if (!isEmpty(el[DATA])) {
delete el[DATA];
}
if (removeEl) {
remove(this.$el);
}
};
UIkit.prototype.$emit = function (e) {
this._callUpdate(e);
};
UIkit.prototype.$update = function (element = this.$el, e) {
UIkit.update(element, e);
};
UIkit.prototype.$getComponent = UIkit.getComponent;
const componentName = memoize(name => UIkit.prefix + hyphenate(name));
Object.defineProperties(UIkit.prototype, {
$container: Object.getOwnPropertyDescriptor(UIkit, 'container'),
$name: {
get() {
return componentName(this.$options.name);
}
}
});
}

View File

@@ -0,0 +1,323 @@
import {assign, camelize, data as getData, hasOwn, hyphenate, isArray, isEmpty, isFunction, isNumeric, isPlainObject, isString, isUndefined, mergeOptions, on, parseOptions, startsWith, toBoolean, toNumber} from 'uikit-util';
export default function (UIkit) {
let uid = 0;
UIkit.prototype._init = function (options) {
options = options || {};
options.data = normalizeData(options, this.constructor.options);
this.$options = mergeOptions(this.constructor.options, options, this);
this.$el = null;
this.$props = {};
this._uid = uid++;
this._initData();
this._initMethods();
this._initComputeds();
this._callHook('created');
if (options.el) {
this.$mount(options.el);
}
};
UIkit.prototype._initData = function () {
const {data = {}} = this.$options;
for (const key in data) {
this.$props[key] = this[key] = data[key];
}
};
UIkit.prototype._initMethods = function () {
const {methods} = this.$options;
if (methods) {
for (const key in methods) {
this[key] = methods[key].bind(this);
}
}
};
UIkit.prototype._initComputeds = function () {
const {computed} = this.$options;
this._computeds = {};
if (computed) {
for (const key in computed) {
registerComputed(this, key, computed[key]);
}
}
};
UIkit.prototype._initProps = function (props) {
let key;
props = props || getProps(this.$options, this.$name);
for (key in props) {
if (!isUndefined(props[key])) {
this.$props[key] = props[key];
}
}
const exclude = [this.$options.computed, this.$options.methods];
for (key in this.$props) {
if (key in props && notIn(exclude, key)) {
this[key] = this.$props[key];
}
}
};
UIkit.prototype._initEvents = function () {
this._events = [];
const {events} = this.$options;
if (events) {
events.forEach(event => {
if (hasOwn(event, 'handler')) {
registerEvent(this, event);
} else {
for (const key in event) {
registerEvent(this, event[key], key);
}
}
});
}
};
UIkit.prototype._unbindEvents = function () {
this._events.forEach(unbind => unbind());
delete this._events;
};
UIkit.prototype._initObservers = function () {
this._observers = [
initChildListObserver(this),
initPropsObserver(this)
];
};
UIkit.prototype._disconnectObservers = function () {
this._observers.forEach(observer =>
observer && observer.disconnect()
);
};
function getProps(opts, name) {
const data = {};
const {args = [], props = {}, el} = opts;
if (!props) {
return data;
}
for (const key in props) {
const prop = hyphenate(key);
let value = getData(el, prop);
if (isUndefined(value)) {
continue;
}
value = props[key] === Boolean && value === ''
? true
: coerce(props[key], value);
if (prop === 'target' && (!value || startsWith(value, '_'))) {
continue;
}
data[key] = value;
}
const options = parseOptions(getData(el, name), args);
for (const key in options) {
const prop = camelize(key);
if (props[prop] !== undefined) {
data[prop] = coerce(props[prop], options[key]);
}
}
return data;
}
function registerComputed(component, key, cb) {
Object.defineProperty(component, key, {
enumerable: true,
get() {
const {_computeds, $props, $el} = component;
if (!hasOwn(_computeds, key)) {
_computeds[key] = (cb.get || cb).call(component, $props, $el);
}
return _computeds[key];
},
set(value) {
const {_computeds} = component;
_computeds[key] = cb.set ? cb.set.call(component, value) : value;
if (isUndefined(_computeds[key])) {
delete _computeds[key];
}
}
});
}
function registerEvent(component, event, key) {
if (!isPlainObject(event)) {
event = ({name: key, handler: event});
}
let {name, el, handler, capture, passive, delegate, filter, self} = event;
el = isFunction(el)
? el.call(component)
: el || component.$el;
if (isArray(el)) {
el.forEach(el => registerEvent(component, assign({}, event, {el}), key));
return;
}
if (!el || filter && !filter.call(component)) {
return;
}
component._events.push(
on(
el,
name,
!delegate
? null
: isString(delegate)
? delegate
: delegate.call(component),
isString(handler) ? component[handler] : handler.bind(component),
{passive, capture, self}
)
);
}
function notIn(options, key) {
return options.every(arr => !arr || !hasOwn(arr, key));
}
function coerce(type, value) {
if (type === Boolean) {
return toBoolean(value);
} else if (type === Number) {
return toNumber(value);
} else if (type === 'list') {
return toList(value);
}
return type ? type(value) : value;
}
function toList(value) {
return isArray(value)
? value
: isString(value)
? value.split(/,(?![^(]*\))/).map(value => isNumeric(value)
? toNumber(value)
: toBoolean(value.trim()))
: [value];
}
function normalizeData({data}, {args, props = {}}) {
data = isArray(data)
? !isEmpty(args)
? data.slice(0, args.length).reduce((data, value, index) => {
if (isPlainObject(value)) {
assign(data, value);
} else {
data[args[index]] = value;
}
return data;
}, {})
: undefined
: data;
if (data) {
for (const key in data) {
if (isUndefined(data[key])) {
delete data[key];
} else {
data[key] = props[key] ? coerce(props[key], data[key]) : data[key];
}
}
}
return data;
}
function initChildListObserver(component) {
const {el} = component.$options;
const observer = new MutationObserver(() => component.$emit());
observer.observe(el, {
childList: true,
subtree: true
});
return observer;
}
function initPropsObserver(component) {
const {$name, $options, $props} = component;
const {attrs, props, el} = $options;
if (!props || attrs === false) {
return;
}
const attributes = isArray(attrs) ? attrs : Object.keys(props);
const filter = attributes.map(key => hyphenate(key)).concat($name);
const observer = new MutationObserver(records => {
const data = getProps($options, $name);
if (records.some(({attributeName}) => {
const prop = attributeName.replace('data-', '');
return (prop === $name ? attributes : [camelize(prop), camelize(attributeName)]).some(prop =>
!isUndefined(data[prop]) && data[prop] !== $props[prop]
);
})) {
component.$reset();
}
});
observer.observe(el, {
attributes: true,
attributeFilter: filter.concat(filter.map(key => `data-${key}`))
});
return observer;
}
}

View File

@@ -0,0 +1,154 @@
import Class from '../mixin/class';
import {$, empty, html} from 'uikit-util';
export default {
mixins: [Class],
props: {
date: String,
clsWrapper: String
},
data: {
date: '',
clsWrapper: '.uk-countdown-%unit%'
},
computed: {
date({date}) {
return Date.parse(date);
},
days({clsWrapper}, $el) {
return $(clsWrapper.replace('%unit%', 'days'), $el);
},
hours({clsWrapper}, $el) {
return $(clsWrapper.replace('%unit%', 'hours'), $el);
},
minutes({clsWrapper}, $el) {
return $(clsWrapper.replace('%unit%', 'minutes'), $el);
},
seconds({clsWrapper}, $el) {
return $(clsWrapper.replace('%unit%', 'seconds'), $el);
},
units() {
return ['days', 'hours', 'minutes', 'seconds'].filter(unit => this[unit]);
}
},
connected() {
this.start();
},
disconnected() {
this.stop();
this.units.forEach(unit => empty(this[unit]));
},
events: [
{
name: 'visibilitychange',
el() {
return document;
},
handler() {
if (document.hidden) {
this.stop();
} else {
this.start();
}
}
}
],
update: {
write() {
const timespan = getTimeSpan(this.date);
if (timespan.total <= 0) {
this.stop();
timespan.days
= timespan.hours
= timespan.minutes
= timespan.seconds
= 0;
}
this.units.forEach(unit => {
let digits = String(Math.floor(timespan[unit]));
digits = digits.length < 2 ? `0${digits}` : digits;
const el = this[unit];
if (el.textContent !== digits) {
digits = digits.split('');
if (digits.length !== el.children.length) {
html(el, digits.map(() => '<span></span>').join(''));
}
digits.forEach((digit, i) => el.children[i].textContent = digit);
}
});
}
},
methods: {
start() {
this.stop();
if (this.date && this.units.length) {
this.$update();
this.timer = setInterval(this.$update, 1000);
}
},
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
}
};
function getTimeSpan(date) {
const total = date - Date.now();
return {
total,
seconds: total / 1000 % 60,
minutes: total / 1000 / 60 % 60,
hours: total / 1000 / 60 / 60 % 24,
days: total / 1000 / 60 / 60 / 24
};
}

View File

@@ -0,0 +1,208 @@
import Animate from '../mixin/animate';
import {$$, append, assign, css, data, each, fastdom, children as getChildren, hasClass, includes, isEmpty, isEqual, isUndefined, matches, parseOptions, Promise, toggleClass, trigger} from 'uikit-util';
export default {
mixins: [Animate],
args: 'target',
props: {
target: Boolean,
selActive: Boolean
},
data: {
target: null,
selActive: false,
attrItem: 'uk-filter-control',
cls: 'uk-active',
duration: 250
},
computed: {
toggles: {
get({attrItem}, $el) {
return $$(`[${attrItem}],[data-${attrItem}]`, $el);
},
watch() {
this.updateState();
if (this.selActive !== false) {
const actives = $$(this.selActive, this.$el);
this.toggles.forEach(el => toggleClass(el, this.cls, includes(actives, el)));
}
},
immediate: true
},
children: {
get({target}, $el) {
return $$(`${target} > *`, $el);
},
watch(list, old) {
if (old && !isEqualList(list, old)) {
this.updateState();
}
},
immediate: true
}
},
events: [
{
name: 'click',
delegate() {
return `[${this.attrItem}],[data-${this.attrItem}]`;
},
handler(e) {
e.preventDefault();
this.apply(e.current);
}
}
],
methods: {
apply(el) {
const prevState = this.getState();
const newState = mergeState(el, this.attrItem, this.getState());
if (!isEqualState(prevState, newState)) {
this.setState(newState);
}
},
getState() {
return this.toggles
.filter(item => hasClass(item, this.cls))
.reduce((state, el) => mergeState(el, this.attrItem, state), {filter: {'': ''}, sort: []});
},
setState(state, animate = true) {
state = assign({filter: {'': ''}, sort: []}, state);
trigger(this.$el, 'beforeFilter', [this, state]);
this.toggles.forEach(el => toggleClass(el, this.cls, !!matchFilter(el, this.attrItem, state)));
Promise.all($$(this.target, this.$el).map(target => {
const filterFn = () => {
applyState(state, target, getChildren(target));
this.$update(this.$el);
};
return animate ? this.animate(filterFn, target) : filterFn();
})).then(() => trigger(this.$el, 'afterFilter', [this]));
},
updateState() {
fastdom.write(() => this.setState(this.getState(), false));
}
}
};
function getFilter(el, attr) {
return parseOptions(data(el, attr), ['filter']);
}
function isEqualState(stateA, stateB) {
return ['filter', 'sort'].every(prop => isEqual(stateA[prop], stateB[prop]));
}
function applyState(state, target, children) {
const selector = getSelector(state);
children.forEach(el => css(el, 'display', selector && !matches(el, selector) ? 'none' : ''));
const [sort, order] = state.sort;
if (sort) {
const sorted = sortItems(children, sort, order);
if (!isEqual(sorted, children)) {
append(target, sorted);
}
}
}
function mergeState(el, attr, state) {
const filterBy = getFilter(el, attr);
const {filter, group, sort, order = 'asc'} = filterBy;
if (filter || isUndefined(sort)) {
if (group) {
if (filter) {
delete state.filter[''];
state.filter[group] = filter;
} else {
delete state.filter[group];
if (isEmpty(state.filter) || '' in state.filter) {
state.filter = {'': filter || ''};
}
}
} else {
state.filter = {'': filter || ''};
}
}
if (!isUndefined(sort)) {
state.sort = [sort, order];
}
return state;
}
function matchFilter(el, attr, {filter: stateFilter = {'': ''}, sort: [stateSort, stateOrder]}) {
const {filter = '', group = '', sort, order = 'asc'} = getFilter(el, attr);
return isUndefined(sort)
? group in stateFilter && filter === stateFilter[group]
|| !filter && group && !(group in stateFilter) && !stateFilter['']
: stateSort === sort && stateOrder === order;
}
function isEqualList(listA, listB) {
return listA.length === listB.length
&& listA.every(el => ~listB.indexOf(el));
}
function getSelector({filter}) {
let selector = '';
each(filter, value => selector += value || '');
return selector;
}
function sortItems(nodes, sort, order) {
return assign([], nodes).sort((a, b) => data(a, sort).localeCompare(data(b, sort), undefined, {numeric: true}) * (order === 'asc' || -1));
}

View File

@@ -0,0 +1,13 @@
export {default as Countdown} from './countdown';
export {default as Filter} from './filter';
export {default as Lightbox} from './lightbox';
export {default as LightboxPanel} from './lightbox-panel';
export {default as Notification} from './notification';
export {default as Parallax} from './parallax';
export {default as Slider} from './slider';
export {default as SliderParallax} from './slider-parallax';
export {default as Slideshow} from './slideshow';
export {default as SlideshowParallax} from './slideshow-parallax';
export {default as Sortable} from './sortable';
export {default as Tooltip} from './tooltip';
export {default as Upload} from './upload';

View File

@@ -0,0 +1,50 @@
import Animations, {scale3d} from '../../mixin/internal/slideshow-animations';
import {assign, css} from 'uikit-util';
export default assign({}, Animations, {
fade: {
show() {
return [
{opacity: 0},
{opacity: 1}
];
},
percent(current) {
return 1 - css(current, 'opacity');
},
translate(percent) {
return [
{opacity: 1 - percent},
{opacity: percent}
];
}
},
scale: {
show() {
return [
{opacity: 0, transform: scale3d(1 - .2)},
{opacity: 1, transform: scale3d(1)}
];
},
percent(current) {
return 1 - css(current, 'opacity');
},
translate(percent) {
return [
{opacity: 1 - percent, transform: scale3d(1 - .2 * percent)},
{opacity: percent, transform: scale3d(1 - .2 + .2 * percent)}
];
}
}
});

View File

@@ -0,0 +1,160 @@
import {translate} from '../../mixin/internal/slideshow-animations';
import {children, clamp, createEvent, css, Deferred, dimensions, findIndex, includes, isRtl, noop, position, Transition, trigger} from 'uikit-util';
export default function (prev, next, dir, {center, easing, list}) {
const deferred = new Deferred();
const from = prev
? getLeft(prev, list, center)
: getLeft(next, list, center) + dimensions(next).width * dir;
const to = next
? getLeft(next, list, center)
: from + dimensions(prev).width * dir * (isRtl ? -1 : 1);
return {
dir,
show(duration, percent = 0, linear) {
const timing = linear ? 'linear' : easing;
duration -= Math.round(duration * clamp(percent, -1, 1));
this.translate(percent);
percent = prev ? percent : clamp(percent, 0, 1);
triggerUpdate(this.getItemIn(), 'itemin', {percent, duration, timing, dir});
prev && triggerUpdate(this.getItemIn(true), 'itemout', {percent: 1 - percent, duration, timing, dir});
Transition
.start(list, {transform: translate(-to * (isRtl ? -1 : 1), 'px')}, duration, timing)
.then(deferred.resolve, noop);
return deferred.promise;
},
cancel() {
Transition.cancel(list);
},
reset() {
css(list, 'transform', '');
},
forward(duration, percent = this.percent()) {
Transition.cancel(list);
return this.show(duration, percent, true);
},
translate(percent) {
const distance = this.getDistance() * dir * (isRtl ? -1 : 1);
css(list, 'transform', translate(clamp(
-to + (distance - distance * percent),
-getWidth(list),
dimensions(list).width
) * (isRtl ? -1 : 1), 'px'));
const actives = this.getActives();
const itemIn = this.getItemIn();
const itemOut = this.getItemIn(true);
percent = prev ? clamp(percent, -1, 1) : 0;
children(list).forEach(slide => {
const isActive = includes(actives, slide);
const isIn = slide === itemIn;
const isOut = slide === itemOut;
const translateIn = isIn || !isOut && (isActive || dir * (isRtl ? -1 : 1) === -1 ^ getElLeft(slide, list) > getElLeft(prev || next));
triggerUpdate(slide, `itemtranslate${translateIn ? 'in' : 'out'}`, {
dir,
percent: isOut
? 1 - percent
: isIn
? percent
: isActive
? 1
: 0
});
});
},
percent() {
return Math.abs((css(list, 'transform').split(',')[4] * (isRtl ? -1 : 1) + from) / (to - from));
},
getDistance() {
return Math.abs(to - from);
},
getItemIn(out = false) {
let actives = this.getActives();
let nextActives = inView(list, getLeft(next || prev, list, center));
if (out) {
const temp = actives;
actives = nextActives;
nextActives = temp;
}
return nextActives[findIndex(nextActives, el => !includes(actives, el))];
},
getActives() {
return inView(list, getLeft(prev || next, list, center));
}
};
}
function getLeft(el, list, center) {
const left = getElLeft(el, list);
return center
? left - centerEl(el, list)
: Math.min(left, getMax(list));
}
export function getMax(list) {
return Math.max(0, getWidth(list) - dimensions(list).width);
}
export function getWidth(list) {
return children(list).reduce((right, el) => dimensions(el).width + right, 0);
}
function centerEl(el, list) {
return dimensions(list).width / 2 - dimensions(el).width / 2;
}
export function getElLeft(el, list) {
return el && (position(el).left + (isRtl ? dimensions(el).width - dimensions(list).width : 0)) * (isRtl ? -1 : 1) || 0;
}
function inView(list, listLeft) {
listLeft -= 1;
const listWidth = dimensions(list).width;
const listRight = listLeft + listWidth + 2;
return children(list).filter(slide => {
const slideLeft = getElLeft(slide, list);
const slideRight = slideLeft + Math.min(dimensions(slide).width, listWidth);
return slideLeft >= listLeft && slideRight <= listRight;
});
}
function triggerUpdate(el, type, data) {
trigger(el, createEvent(type, false, false, data));
}

View File

@@ -0,0 +1,118 @@
import Animations, {scale3d, translate, translated} from '../../mixin/internal/slideshow-animations';
import {assign, css} from 'uikit-util';
export default assign({}, Animations, {
fade: {
show() {
return [
{opacity: 0, zIndex: 0},
{zIndex: -1}
];
},
percent(current) {
return 1 - css(current, 'opacity');
},
translate(percent) {
return [
{opacity: 1 - percent, zIndex: 0},
{zIndex: -1}
];
}
},
scale: {
show() {
return [
{opacity: 0, transform: scale3d(1 + .5), zIndex: 0},
{zIndex: -1}
];
},
percent(current) {
return 1 - css(current, 'opacity');
},
translate(percent) {
return [
{opacity: 1 - percent, transform: scale3d(1 + .5 * percent), zIndex: 0},
{zIndex: -1}
];
}
},
pull: {
show(dir) {
return dir < 0
? [
{transform: translate(30), zIndex: -1},
{transform: translate(), zIndex: 0}
]
: [
{transform: translate(-100), zIndex: 0},
{transform: translate(), zIndex: -1}
];
},
percent(current, next, dir) {
return dir < 0
? 1 - translated(next)
: translated(current);
},
translate(percent, dir) {
return dir < 0
? [
{transform: translate(30 * percent), zIndex: -1},
{transform: translate(-100 * (1 - percent)), zIndex: 0}
]
: [
{transform: translate(-percent * 100), zIndex: 0},
{transform: translate(30 * (1 - percent)), zIndex: -1}
];
}
},
push: {
show(dir) {
return dir < 0
? [
{transform: translate(100), zIndex: 0},
{transform: translate(), zIndex: -1}
]
: [
{transform: translate(-30), zIndex: -1},
{transform: translate(), zIndex: 0}
];
},
percent(current, next, dir) {
return dir > 0
? 1 - translated(next)
: translated(current);
},
translate(percent, dir) {
return dir < 0
? [
{transform: translate(percent * 100), zIndex: 0},
{transform: translate(-30 * (1 - percent)), zIndex: -1}
]
: [
{transform: translate(-30 * percent), zIndex: -1},
{transform: translate(100 * (1 - percent)), zIndex: 0}
];
}
}
});

View File

@@ -0,0 +1,349 @@
import Animations from './internal/lightbox-animations';
import Container from '../mixin/container';
import Modal from '../mixin/modal';
import Slideshow from '../mixin/slideshow';
import Togglable from '../mixin/togglable';
import {$, addClass, ajax, append, assign, attr, fragment, getImage, getIndex, html, on, pointerDown, pointerMove, removeClass, Transition, trigger} from 'uikit-util';
export default {
mixins: [Container, Modal, Togglable, Slideshow],
functional: true,
props: {
delayControls: Number,
preload: Number,
videoAutoplay: Boolean,
template: String
},
data: () => ({
preload: 1,
videoAutoplay: false,
delayControls: 3000,
items: [],
cls: 'uk-open',
clsPage: 'uk-lightbox-page',
selList: '.uk-lightbox-items',
attrItem: 'uk-lightbox-item',
selClose: '.uk-close-large',
selCaption: '.uk-lightbox-caption',
pauseOnHover: false,
velocity: 2,
Animations,
template: `<div class="uk-lightbox uk-overflow-hidden">
<ul class="uk-lightbox-items"></ul>
<div class="uk-lightbox-toolbar uk-position-top uk-text-right uk-transition-slide-top uk-transition-opaque">
<button class="uk-lightbox-toolbar-icon uk-close-large" type="button" uk-close></button>
</div>
<a class="uk-lightbox-button uk-position-center-left uk-position-medium uk-transition-fade" href uk-slidenav-previous uk-lightbox-item="previous"></a>
<a class="uk-lightbox-button uk-position-center-right uk-position-medium uk-transition-fade" href uk-slidenav-next uk-lightbox-item="next"></a>
<div class="uk-lightbox-toolbar uk-lightbox-caption uk-position-bottom uk-text-center uk-transition-slide-bottom uk-transition-opaque"></div>
</div>`
}),
created() {
const $el = $(this.template);
const list = $(this.selList, $el);
this.items.forEach(() => append(list, '<li>'));
this.$mount(append(this.container, $el));
},
computed: {
caption({selCaption}, $el) {
return $(selCaption, $el);
}
},
events: [
{
name: `${pointerMove} ${pointerDown} keydown`,
handler: 'showControls'
},
{
name: 'click',
self: true,
delegate() {
return this.selSlides;
},
handler(e) {
if (e.defaultPrevented) {
return;
}
this.hide();
}
},
{
name: 'shown',
self: true,
handler() {
this.showControls();
}
},
{
name: 'hide',
self: true,
handler() {
this.hideControls();
removeClass(this.slides, this.clsActive);
Transition.stop(this.slides);
}
},
{
name: 'hidden',
self: true,
handler() {
this.$destroy(true);
}
},
{
name: 'keyup',
el() {
return document;
},
handler(e) {
if (!this.isToggled(this.$el) || !this.draggable) {
return;
}
switch (e.keyCode) {
case 37:
this.show('previous');
break;
case 39:
this.show('next');
break;
}
}
},
{
name: 'beforeitemshow',
handler(e) {
if (this.isToggled()) {
return;
}
this.draggable = false;
e.preventDefault();
this.toggleElement(this.$el, true, false);
this.animation = Animations['scale'];
removeClass(e.target, this.clsActive);
this.stack.splice(1, 0, this.index);
}
},
{
name: 'itemshow',
handler() {
html(this.caption, this.getItem().caption || '');
for (let j = -this.preload; j <= this.preload; j++) {
this.loadItem(this.index + j);
}
}
},
{
name: 'itemshown',
handler() {
this.draggable = this.$props.draggable;
}
},
{
name: 'itemload',
handler(_, item) {
const {source: src, type, alt = '', poster, attrs = {}} = item;
this.setItem(item, '<span uk-spinner></span>');
if (!src) {
return;
}
let matches;
const iframeAttrs = {
frameborder: '0',
allow: 'autoplay',
allowfullscreen: '',
style: 'max-width: 100%; box-sizing: border-box;',
'uk-responsive': '',
'uk-video': `${this.videoAutoplay}`
};
// Image
if (type === 'image' || src.match(/\.(avif|jpe?g|a?png|gif|svg|webp)($|\?)/i)) {
getImage(src, attrs.srcset, attrs.size).then(
({width, height}) => this.setItem(item, createEl('img', assign({src, width, height, alt}, attrs))),
() => this.setError(item)
);
// Video
} else if (type === 'video' || src.match(/\.(mp4|webm|ogv)($|\?)/i)) {
const video = createEl('video', assign({
src,
poster,
controls: '',
playsinline: '',
'uk-video': `${this.videoAutoplay}`
}, attrs));
on(video, 'loadedmetadata', () => {
attr(video, {width: video.videoWidth, height: video.videoHeight});
this.setItem(item, video);
});
on(video, 'error', () => this.setError(item));
// Iframe
} else if (type === 'iframe' || src.match(/\.(html|php)($|\?)/i)) {
this.setItem(item, createEl('iframe', assign({
src,
frameborder: '0',
allowfullscreen: '',
class: 'uk-lightbox-iframe'
}, attrs)));
// YouTube
} else if ((matches = src.match(/\/\/(?:.*?youtube(-nocookie)?\..*?[?&]v=|youtu\.be\/)([\w-]{11})[&?]?(.*)?/))) {
this.setItem(item, createEl('iframe', assign({
src: `https://www.youtube${matches[1] || ''}.com/embed/${matches[2]}${matches[3] ? `?${matches[3]}` : ''}`,
width: 1920,
height: 1080
}, iframeAttrs, attrs)));
// Vimeo
} else if ((matches = src.match(/\/\/.*?vimeo\.[a-z]+\/(\d+)[&?]?(.*)?/))) {
ajax(`https://vimeo.com/api/oembed.json?maxwidth=1920&url=${encodeURI(src)}`, {
responseType: 'json',
withCredentials: false
}).then(
({response: {height, width}}) => this.setItem(item, createEl('iframe', assign({
src: `https://player.vimeo.com/video/${matches[1]}${matches[2] ? `?${matches[2]}` : ''}`,
width,
height
}, iframeAttrs, attrs))),
() => this.setError(item)
);
}
}
}
],
methods: {
loadItem(index = this.index) {
const item = this.getItem(index);
if (!this.getSlide(item).childElementCount) {
trigger(this.$el, 'itemload', [item]);
}
},
getItem(index = this.index) {
return this.items[getIndex(index, this.slides)];
},
setItem(item, content) {
trigger(this.$el, 'itemloaded', [this, html(this.getSlide(item), content) ]);
},
getSlide(item) {
return this.slides[this.items.indexOf(item)];
},
setError(item) {
this.setItem(item, '<span uk-icon="icon: bolt; ratio: 2"></span>');
},
showControls() {
clearTimeout(this.controlsTimer);
this.controlsTimer = setTimeout(this.hideControls, this.delayControls);
addClass(this.$el, 'uk-active', 'uk-transition-active');
},
hideControls() {
removeClass(this.$el, 'uk-active', 'uk-transition-active');
}
}
};
function createEl(tag, attrs) {
const el = fragment(`<${tag}>`);
attr(el, attrs);
return el;
}

View File

@@ -0,0 +1,104 @@
import LightboxPanel from './lightbox-panel';
import {$$, assign, data, findIndex, isElement, on, parseOptions, uniqueBy} from 'uikit-util';
export default {
install,
props: {toggle: String},
data: {toggle: 'a'},
computed: {
toggles: {
get({toggle}, $el) {
return $$(toggle, $el);
},
watch() {
this.hide();
}
}
},
disconnected() {
this.hide();
},
events: [
{
name: 'click',
delegate() {
return `${this.toggle}:not(.uk-disabled)`;
},
handler(e) {
e.preventDefault();
this.show(e.current);
}
}
],
methods: {
show(index) {
const items = uniqueBy(this.toggles.map(toItem), 'source');
if (isElement(index)) {
const {source} = toItem(index);
index = findIndex(items, ({source: src}) => source === src);
}
this.panel = this.panel || this.$create('lightboxPanel', assign({}, this.$props, {items}));
on(this.panel.$el, 'hidden', () => this.panel = false);
return this.panel.show(index);
},
hide() {
return this.panel && this.panel.hide();
}
}
};
function install(UIkit, Lightbox) {
if (!UIkit.lightboxPanel) {
UIkit.component('lightboxPanel', LightboxPanel);
}
assign(
Lightbox.props,
UIkit.component('lightboxPanel').options.props
);
}
function toItem(el) {
const item = {};
['href', 'caption', 'type', 'poster', 'alt', 'attrs'].forEach(attr => {
item[attr === 'href' ? 'source' : attr] = data(el, attr);
});
item.attrs = parseOptions(item.attrs);
return item;
}

View File

@@ -0,0 +1,129 @@
import Container from '../mixin/container';
import {$, append, apply, closest, css, parent, pointerEnter, pointerLeave, remove, startsWith, toFloat, Transition, trigger} from 'uikit-util';
export default {
mixins: [Container],
functional: true,
args: ['message', 'status'],
data: {
message: '',
status: '',
timeout: 5000,
group: null,
pos: 'top-center',
clsContainer: 'uk-notification',
clsClose: 'uk-notification-close',
clsMsg: 'uk-notification-message'
},
install,
computed: {
marginProp({pos}) {
return `margin${startsWith(pos, 'top') ? 'Top' : 'Bottom'}`;
},
startProps() {
return {opacity: 0, [this.marginProp]: -this.$el.offsetHeight};
}
},
created() {
const container = $(`.${this.clsContainer}-${this.pos}`, this.container)
|| append(this.container, `<div class="${this.clsContainer} ${this.clsContainer}-${this.pos}" style="display: block"></div>`);
this.$mount(append(container,
`<div class="${this.clsMsg}${this.status ? ` ${this.clsMsg}-${this.status}` : ''}">
<a href class="${this.clsClose}" data-uk-close></a>
<div>${this.message}</div>
</div>`
));
},
connected() {
const margin = toFloat(css(this.$el, this.marginProp));
Transition.start(
css(this.$el, this.startProps),
{opacity: 1, [this.marginProp]: margin}
).then(() => {
if (this.timeout) {
this.timer = setTimeout(this.close, this.timeout);
}
});
},
events: {
click(e) {
if (closest(e.target, 'a[href="#"],a[href=""]')) {
e.preventDefault();
}
this.close();
},
[pointerEnter]() {
if (this.timer) {
clearTimeout(this.timer);
}
},
[pointerLeave]() {
if (this.timeout) {
this.timer = setTimeout(this.close, this.timeout);
}
}
},
methods: {
close(immediate) {
const removeFn = el => {
const container = parent(el);
trigger(el, 'close', [this]);
remove(el);
if (container && !container.hasChildNodes()) {
remove(container);
}
};
if (this.timer) {
clearTimeout(this.timer);
}
if (immediate) {
removeFn(this.$el);
} else {
Transition.start(this.$el, this.startProps).then(removeFn);
}
}
}
};
function install(UIkit) {
UIkit.notification.closeAll = function (group, immediate) {
apply(document.body, el => {
const notification = UIkit.getComponent(el, 'notification');
if (notification && (!group || group === notification.group)) {
notification.close(immediate);
}
});
};
}

View File

@@ -0,0 +1,76 @@
import Parallax from '../mixin/parallax';
import {clamp, css, parent, query, scrolledOver} from 'uikit-util';
export default {
mixins: [Parallax],
props: {
target: String,
viewport: Number,
easing: Number
},
data: {
target: false,
viewport: 1,
easing: 1
},
computed: {
target({target}, $el) {
return getOffsetElement(target && query(target, $el) || $el);
}
},
update: {
read({percent}, types) {
if (!types.has('scroll')) {
percent = false;
}
if (!this.matchMedia) {
return;
}
const prev = percent;
percent = ease(scrolledOver(this.target) / (this.viewport || 1), this.easing);
return {
percent,
style: prev !== percent ? this.getCss(percent) : false
};
},
write({style}) {
if (!this.matchMedia) {
this.reset();
return;
}
style && css(this.$el, style);
},
events: ['scroll', 'resize']
}
};
function ease(percent, easing) {
return clamp(percent * (1 - (easing - easing * percent)));
}
// SVG elements do not inherit from HTMLElement
function getOffsetElement(el) {
return el
? 'offsetTop' in el
? el
: getOffsetElement(parent(el))
: document.body;
}

View File

@@ -0,0 +1,90 @@
import Parallax from '../mixin/parallax';
import {css, endsWith, fastdom, noop, query, Transition} from 'uikit-util';
export default {
mixins: [Parallax],
data: {
selItem: '!li'
},
computed: {
item({selItem}, $el) {
return query(selItem, $el);
}
},
events: [
{
name: 'itemin itemout',
self: true,
el() {
return this.item;
},
handler({type, detail: {percent, duration, timing, dir}}) {
fastdom.read(() => {
const propsFrom = this.getCss(getCurrentPercent(type, dir, percent));
const propsTo = this.getCss(isIn(type) ? .5 : dir > 0 ? 1 : 0);
fastdom.write(() => {
css(this.$el, propsFrom);
Transition.start(this.$el, propsTo, duration, timing).catch(noop);
});
});
}
},
{
name: 'transitioncanceled transitionend',
self: true,
el() {
return this.item;
},
handler() {
Transition.cancel(this.$el);
}
},
{
name: 'itemtranslatein itemtranslateout',
self: true,
el() {
return this.item;
},
handler({type, detail: {percent, dir}}) {
fastdom.read(() => {
const props = this.getCss(getCurrentPercent(type, dir, percent));
fastdom.write(() => css(this.$el, props));
});
}
}
]
};
function isIn(type) {
return endsWith(type, 'in');
}
function getCurrentPercent(type, dir, percent) {
percent /= 2;
return isIn(type) ^ dir < 0 ? percent : 1 - percent;
}

View File

@@ -0,0 +1,255 @@
import Class from '../mixin/class';
import Slider, {speedUp} from '../mixin/slider';
import SliderReactive from '../mixin/slider-reactive';
import Transitioner, {getMax, getWidth} from './internal/slider-transitioner';
import {$, addClass, children, css, data, dimensions, findIndex, includes, isEmpty, last, sortBy, toFloat, toggleClass, toNumber} from 'uikit-util';
export default {
mixins: [Class, Slider, SliderReactive],
props: {
center: Boolean,
sets: Boolean
},
data: {
center: false,
sets: false,
attrItem: 'uk-slider-item',
selList: '.uk-slider-items',
selNav: '.uk-slider-nav',
clsContainer: 'uk-slider-container',
Transitioner
},
computed: {
avgWidth() {
return getWidth(this.list) / this.length;
},
finite({finite}) {
return finite || Math.ceil(getWidth(this.list)) < dimensions(this.list).width + getMaxElWidth(this.list) + this.center;
},
maxIndex() {
if (!this.finite || this.center && !this.sets) {
return this.length - 1;
}
if (this.center) {
return last(this.sets);
}
let lft = 0;
const max = getMax(this.list);
const index = findIndex(this.slides, el => {
if (lft >= max) {
return true;
}
lft += dimensions(el).width;
});
return ~index ? index : this.length - 1;
},
sets({sets}) {
if (!sets) {
return;
}
const width = dimensions(this.list).width / (this.center ? 2 : 1);
let left = 0;
let leftCenter = width;
let slideLeft = 0;
sets = sortBy(this.slides, 'offsetLeft').reduce((sets, slide, i) => {
const slideWidth = dimensions(slide).width;
const slideRight = slideLeft + slideWidth;
if (slideRight > left) {
if (!this.center && i > this.maxIndex) {
i = this.maxIndex;
}
if (!includes(sets, i)) {
const cmp = this.slides[i + 1];
if (this.center && cmp && slideWidth < leftCenter - dimensions(cmp).width / 2) {
leftCenter -= slideWidth;
} else {
leftCenter = width;
sets.push(i);
left = slideLeft + width + (this.center ? slideWidth / 2 : 0);
}
}
}
slideLeft += slideWidth;
return sets;
}, []);
return !isEmpty(sets) && sets;
},
transitionOptions() {
return {
center: this.center,
list: this.list
};
}
},
connected() {
toggleClass(this.$el, this.clsContainer, !$(`.${this.clsContainer}`, this.$el));
},
update: {
write() {
this.navItems.forEach(el => {
const index = toNumber(data(el, this.attrItem));
if (index !== false) {
el.hidden = !this.maxIndex || index > this.maxIndex || this.sets && !includes(this.sets, index);
}
});
if (this.length && !this.dragging && !this.stack.length) {
this.reorder();
this._translate(1);
}
const actives = this._getTransitioner(this.index).getActives();
this.slides.forEach(slide => toggleClass(slide, this.clsActive, includes(actives, slide)));
if (this.clsActivated && (!this.sets || includes(this.sets, toFloat(this.index)))) {
this.slides.forEach(slide => toggleClass(slide, this.clsActivated || '', includes(actives, slide)));
}
},
events: ['resize']
},
events: {
beforeitemshow(e) {
if (!this.dragging && this.sets && this.stack.length < 2 && !includes(this.sets, this.index)) {
this.index = this.getValidIndex();
}
const diff = Math.abs(
this.index
- this.prevIndex
+ (this.dir > 0 && this.index < this.prevIndex || this.dir < 0 && this.index > this.prevIndex ? (this.maxIndex + 1) * this.dir : 0)
);
if (!this.dragging && diff > 1) {
for (let i = 0; i < diff; i++) {
this.stack.splice(1, 0, this.dir > 0 ? 'next' : 'previous');
}
e.preventDefault();
return;
}
const index = this.dir < 0 || !this.slides[this.prevIndex] ? this.index : this.prevIndex;
this.duration = speedUp(this.avgWidth / this.velocity) * (dimensions(this.slides[index]).width / this.avgWidth);
this.reorder();
},
itemshow() {
if (~this.prevIndex) {
addClass(this._getTransitioner().getItemIn(), this.clsActive);
}
}
},
methods: {
reorder() {
if (this.finite) {
css(this.slides, 'order', '');
return;
}
const index = this.dir > 0 && this.slides[this.prevIndex] ? this.prevIndex : this.index;
this.slides.forEach((slide, i) =>
css(slide, 'order', this.dir > 0 && i < index
? 1
: this.dir < 0 && i >= this.index
? -1
: ''
)
);
if (!this.center) {
return;
}
const next = this.slides[index];
let width = dimensions(this.list).width / 2 - dimensions(next).width / 2;
let j = 0;
while (width > 0) {
const slideIndex = this.getIndex(--j + index, index);
const slide = this.slides[slideIndex];
css(slide, 'order', slideIndex > index ? -2 : -1);
width -= dimensions(slide).width;
}
},
getValidIndex(index = this.index, prevIndex = this.prevIndex) {
index = this.getIndex(index, prevIndex);
if (!this.sets) {
return index;
}
let prev;
do {
if (includes(this.sets, index)) {
return index;
}
prev = index;
index = this.getIndex(index + this.dir, prevIndex);
} while (index !== prev);
return index;
}
}
};
function getMaxElWidth(list) {
return Math.max(0, ...children(list).map(el => dimensions(el).width));
}

View File

@@ -0,0 +1 @@
export {default} from './slider-parallax';

View File

@@ -0,0 +1,58 @@
import Class from '../mixin/class';
import Slideshow from '../mixin/slideshow';
import Animations from './internal/slideshow-animations';
import SliderReactive from '../mixin/slider-reactive';
import {boxModelAdjust, css} from 'uikit-util';
export default {
mixins: [Class, Slideshow, SliderReactive],
props: {
ratio: String,
minHeight: Number,
maxHeight: Number
},
data: {
ratio: '16:9',
minHeight: false,
maxHeight: false,
selList: '.uk-slideshow-items',
attrItem: 'uk-slideshow-item',
selNav: '.uk-slideshow-nav',
Animations
},
update: {
read() {
if (!this.list) {
return false;
}
let [width, height] = this.ratio.split(':').map(Number);
height = height * this.list.offsetWidth / width || 0;
if (this.minHeight) {
height = Math.max(this.minHeight, height);
}
if (this.maxHeight) {
height = Math.min(this.maxHeight, height);
}
return {height: height - boxModelAdjust(this.list, 'height', 'content-box')};
},
write({height}) {
height > 0 && css(this.list, 'minHeight', height);
},
events: ['resize']
}
};

View File

@@ -0,0 +1,424 @@
import Animate from '../mixin/animate';
import Class from '../mixin/class';
import {$$, addClass, append, assign, before, children, css, findIndex, getEventPos, getViewport, hasTouch, height, index, isEmpty, isInput, off, offset, on, parent, pointerDown, pointerMove, pointerUp, pointInRect, remove, removeClass, scrollParents, scrollTop, toggleClass, Transition, trigger, within} from 'uikit-util';
export default {
mixins: [Class, Animate],
props: {
group: String,
threshold: Number,
clsItem: String,
clsPlaceholder: String,
clsDrag: String,
clsDragState: String,
clsBase: String,
clsNoDrag: String,
clsEmpty: String,
clsCustom: String,
handle: String
},
data: {
group: false,
threshold: 5,
clsItem: 'uk-sortable-item',
clsPlaceholder: 'uk-sortable-placeholder',
clsDrag: 'uk-sortable-drag',
clsDragState: 'uk-drag',
clsBase: 'uk-sortable',
clsNoDrag: 'uk-sortable-nodrag',
clsEmpty: 'uk-sortable-empty',
clsCustom: '',
handle: false,
pos: {}
},
created() {
['init', 'start', 'move', 'end'].forEach(key => {
const fn = this[key];
this[key] = e => {
assign(this.pos, getEventPos(e));
fn(e);
};
});
},
events: {
name: pointerDown,
passive: false,
handler: 'init'
},
computed: {
target() {
return (this.$el.tBodies || [this.$el])[0];
},
items() {
return children(this.target);
},
isEmpty: {
get() {
return isEmpty(this.items);
},
watch(empty) {
toggleClass(this.target, this.clsEmpty, empty);
},
immediate: true
},
handles: {
get({handle}, el) {
return handle ? $$(handle, el) : this.items;
},
watch(handles, prev) {
css(prev, {touchAction: '', userSelect: ''});
css(handles, {touchAction: hasTouch ? 'none' : '', userSelect: 'none'}); // touchAction set to 'none' causes a performance drop in Chrome 80
},
immediate: true
}
},
update: {
write(data) {
if (!this.drag || !parent(this.placeholder)) {
return;
}
const {pos: {x, y}, origin: {offsetTop, offsetLeft}, placeholder} = this;
css(this.drag, {
top: y - offsetTop,
left: x - offsetLeft
});
const sortable = this.getSortable(document.elementFromPoint(x, y));
if (!sortable) {
return;
}
const {items} = sortable;
if (items.some(Transition.inProgress)) {
return;
}
const target = findTarget(items, {x, y});
if (items.length && (!target || target === placeholder)) {
return;
}
const previous = this.getSortable(placeholder);
const insertTarget = findInsertTarget(sortable.target, target, placeholder, x, y, sortable === previous && data.moved !== target);
if (insertTarget === false) {
return;
}
if (insertTarget && placeholder === insertTarget) {
return;
}
if (sortable !== previous) {
previous.remove(placeholder);
data.moved = target;
} else {
delete data.moved;
}
sortable.insert(placeholder, insertTarget);
this.touched.add(sortable);
},
events: ['move']
},
methods: {
init(e) {
const {target, button, defaultPrevented} = e;
const [placeholder] = this.items.filter(el => within(target, el));
if (!placeholder
|| defaultPrevented
|| button > 0
|| isInput(target)
|| within(target, `.${this.clsNoDrag}`)
|| this.handle && !within(target, this.handle)
) {
return;
}
e.preventDefault();
this.touched = new Set([this]);
this.placeholder = placeholder;
this.origin = assign({target, index: index(placeholder)}, this.pos);
on(document, pointerMove, this.move);
on(document, pointerUp, this.end);
if (!this.threshold) {
this.start(e);
}
},
start(e) {
this.drag = appendDrag(this.$container, this.placeholder);
const {left, top} = this.placeholder.getBoundingClientRect();
assign(this.origin, {offsetLeft: this.pos.x - left, offsetTop: this.pos.y - top});
addClass(this.drag, this.clsDrag, this.clsCustom);
addClass(this.placeholder, this.clsPlaceholder);
addClass(this.items, this.clsItem);
addClass(document.documentElement, this.clsDragState);
trigger(this.$el, 'start', [this, this.placeholder]);
trackScroll(this.pos);
this.move(e);
},
move(e) {
if (this.drag) {
this.$emit('move');
} else if (Math.abs(this.pos.x - this.origin.x) > this.threshold || Math.abs(this.pos.y - this.origin.y) > this.threshold) {
this.start(e);
}
},
end() {
off(document, pointerMove, this.move);
off(document, pointerUp, this.end);
off(window, 'scroll', this.scroll);
if (!this.drag) {
return;
}
untrackScroll();
const sortable = this.getSortable(this.placeholder);
if (this === sortable) {
if (this.origin.index !== index(this.placeholder)) {
trigger(this.$el, 'moved', [this, this.placeholder]);
}
} else {
trigger(sortable.$el, 'added', [sortable, this.placeholder]);
trigger(this.$el, 'removed', [this, this.placeholder]);
}
trigger(this.$el, 'stop', [this, this.placeholder]);
remove(this.drag);
this.drag = null;
this.touched.forEach(({clsPlaceholder, clsItem}) =>
this.touched.forEach(sortable =>
removeClass(sortable.items, clsPlaceholder, clsItem)
)
);
this.touched = null;
removeClass(document.documentElement, this.clsDragState);
},
insert(element, target) {
addClass(this.items, this.clsItem);
const insert = () => target
? before(target, element)
: append(this.target, element);
this.animate(insert);
},
remove(element) {
if (!within(element, this.target)) {
return;
}
this.animate(() => remove(element));
},
getSortable(element) {
do {
const sortable = this.$getComponent(element, 'sortable');
if (sortable && (sortable === this || this.group !== false && sortable.group === this.group)) {
return sortable;
}
} while ((element = parent(element)));
}
}
};
let trackTimer;
function trackScroll(pos) {
let last = Date.now();
trackTimer = setInterval(() => {
let {x, y} = pos;
y += window.pageYOffset;
const dist = (Date.now() - last) * .3;
last = Date.now();
scrollParents(document.elementFromPoint(x, pos.y), /auto|scroll/).reverse().some(scrollEl => {
let {scrollTop: scroll, scrollHeight} = scrollEl;
const {top, bottom, height} = offset(getViewport(scrollEl));
if (top < y && top + 35 > y) {
scroll -= dist;
} else if (bottom > y && bottom - 35 < y) {
scroll += dist;
} else {
return;
}
if (scroll > 0 && scroll < scrollHeight - height) {
scrollTop(scrollEl, scroll);
return true;
}
});
}, 15);
}
function untrackScroll() {
clearInterval(trackTimer);
}
function appendDrag(container, element) {
const clone = append(container, element.outerHTML.replace(/(^<)(?:li|tr)|(?:li|tr)(\/>$)/g, '$1div$2'));
css(clone, 'margin', '0', 'important');
css(clone, assign({
boxSizing: 'border-box',
width: element.offsetWidth,
height: element.offsetHeight
}, css(element, ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'])));
height(clone.firstElementChild, height(element.firstElementChild));
return clone;
}
function findTarget(items, point) {
return items[findIndex(items, item => pointInRect(point, item.getBoundingClientRect()))];
}
function findInsertTarget(list, target, placeholder, x, y, sameList) {
if (!children(list).length) {
return;
}
const rect = target.getBoundingClientRect();
if (!sameList) {
if (!isHorizontal(list, placeholder)) {
return y < rect.top + rect.height / 2
? target
: target.nextElementSibling;
}
return target;
}
const placeholderRect = placeholder.getBoundingClientRect();
const sameRow = linesIntersect(
[rect.top, rect.bottom],
[placeholderRect.top, placeholderRect.bottom]
);
const pointerPos = sameRow ? x : y;
const lengthProp = sameRow ? 'width' : 'height';
const startProp = sameRow ? 'left' : 'top';
const endProp = sameRow ? 'right' : 'bottom';
const diff = placeholderRect[lengthProp] < rect[lengthProp] ? rect[lengthProp] - placeholderRect[lengthProp] : 0;
if (placeholderRect[startProp] < rect[startProp]) {
if (diff && pointerPos < rect[startProp] + diff) {
return false;
}
return target.nextElementSibling;
}
if (diff && pointerPos > rect[endProp] - diff) {
return false;
}
return target;
}
function isHorizontal(list, placeholder) {
const single = children(list).length === 1;
if (single) {
append(list, placeholder);
}
const items = children(list);
const isHorizontal = items.some((el, i) => {
const rectA = el.getBoundingClientRect();
return items.slice(i + 1).some(el => {
const rectB = el.getBoundingClientRect();
return !linesIntersect([rectA.left, rectA.right], [rectB.left, rectB.right]);
});
});
if (single) {
remove(placeholder);
}
return isHorizontal;
}
function linesIntersect(lineA, lineB) {
return lineA[1] > lineB[0] && lineB[1] > lineA[0];
}

View File

@@ -0,0 +1,136 @@
import Container from '../mixin/container';
import Togglable from '../mixin/togglable';
import Position from '../mixin/position';
import {append, attr, flipPosition, hasAttr, isFocusable, isTouch, matches, on, once, pointerDown, pointerEnter, pointerLeave, remove, within} from 'uikit-util';
export default {
mixins: [Container, Togglable, Position],
args: 'title',
props: {
delay: Number,
title: String
},
data: {
pos: 'top',
title: '',
delay: 0,
animation: ['uk-animation-scale-up'],
duration: 100,
cls: 'uk-active',
clsPos: 'uk-tooltip'
},
beforeConnect() {
this._hasTitle = hasAttr(this.$el, 'title');
attr(this.$el, 'title', '');
this.updateAria(false);
makeFocusable(this.$el);
},
disconnected() {
this.hide();
attr(this.$el, 'title', this._hasTitle ? this.title : null);
},
methods: {
show() {
if (this.isToggled(this.tooltip || null) || !this.title) {
return;
}
this._unbind = once(document, `show keydown ${pointerDown}`, this.hide, false, e =>
e.type === pointerDown && !within(e.target, this.$el)
|| e.type === 'keydown' && e.keyCode === 27
|| e.type === 'show' && e.detail[0] !== this && e.detail[0].$name === this.$name
);
clearTimeout(this.showTimer);
this.showTimer = setTimeout(this._show, this.delay);
},
hide() {
if (matches(this.$el, 'input:focus')) {
return;
}
clearTimeout(this.showTimer);
if (!this.isToggled(this.tooltip || null)) {
return;
}
this.toggleElement(this.tooltip, false, false).then(() => {
remove(this.tooltip);
this.tooltip = null;
this._unbind();
});
},
_show() {
this.tooltip = append(this.container,
`<div class="${this.clsPos}">
<div class="${this.clsPos}-inner">${this.title}</div>
</div>`
);
on(this.tooltip, 'toggled', (e, toggled) => {
this.updateAria(toggled);
if (!toggled) {
return;
}
this.positionAt(this.tooltip, this.$el);
this.origin = this.getAxis() === 'y'
? `${flipPosition(this.dir)}-${this.align}`
: `${this.align}-${flipPosition(this.dir)}`;
});
this.toggleElement(this.tooltip, true);
},
updateAria(toggled) {
attr(this.$el, 'aria-expanded', toggled);
}
},
events: {
focus: 'show',
blur: 'hide',
[`${pointerEnter} ${pointerLeave}`](e) {
if (!isTouch(e)) {
this[e.type === pointerEnter ? 'show' : 'hide']();
}
},
// Clicking a button does not give it focus on all browsers and platforms
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
[pointerDown](e) {
if (isTouch(e)) {
this.show();
}
}
}
};
function makeFocusable(el) {
if (!isFocusable(el)) {
attr(el, 'tabindex', '0');
}
}

View File

@@ -0,0 +1,202 @@
import {addClass, ajax, matches, noop, on, removeClass, trigger} from 'uikit-util';
export default {
props: {
allow: String,
clsDragover: String,
concurrent: Number,
maxSize: Number,
method: String,
mime: String,
msgInvalidMime: String,
msgInvalidName: String,
msgInvalidSize: String,
multiple: Boolean,
name: String,
params: Object,
type: String,
url: String
},
data: {
allow: false,
clsDragover: 'uk-dragover',
concurrent: 1,
maxSize: 0,
method: 'POST',
mime: false,
msgInvalidMime: 'Invalid File Type: %s',
msgInvalidName: 'Invalid File Name: %s',
msgInvalidSize: 'Invalid File Size: %s Kilobytes Max',
multiple: false,
name: 'files[]',
params: {},
type: '',
url: '',
abort: noop,
beforeAll: noop,
beforeSend: noop,
complete: noop,
completeAll: noop,
error: noop,
fail: noop,
load: noop,
loadEnd: noop,
loadStart: noop,
progress: noop
},
events: {
change(e) {
if (!matches(e.target, 'input[type="file"]')) {
return;
}
e.preventDefault();
if (e.target.files) {
this.upload(e.target.files);
}
e.target.value = '';
},
drop(e) {
stop(e);
const transfer = e.dataTransfer;
if (!transfer || !transfer.files) {
return;
}
removeClass(this.$el, this.clsDragover);
this.upload(transfer.files);
},
dragenter(e) {
stop(e);
},
dragover(e) {
stop(e);
addClass(this.$el, this.clsDragover);
},
dragleave(e) {
stop(e);
removeClass(this.$el, this.clsDragover);
}
},
methods: {
upload(files) {
if (!files.length) {
return;
}
trigger(this.$el, 'upload', [files]);
for (let i = 0; i < files.length; i++) {
if (this.maxSize && this.maxSize * 1000 < files[i].size) {
this.fail(this.msgInvalidSize.replace('%s', this.maxSize));
return;
}
if (this.allow && !match(this.allow, files[i].name)) {
this.fail(this.msgInvalidName.replace('%s', this.allow));
return;
}
if (this.mime && !match(this.mime, files[i].type)) {
this.fail(this.msgInvalidMime.replace('%s', this.mime));
return;
}
}
if (!this.multiple) {
files = [files[0]];
}
this.beforeAll(this, files);
const chunks = chunk(files, this.concurrent);
const upload = files => {
const data = new FormData();
files.forEach(file => data.append(this.name, file));
for (const key in this.params) {
data.append(key, this.params[key]);
}
ajax(this.url, {
data,
method: this.method,
responseType: this.type,
beforeSend: env => {
const {xhr} = env;
xhr.upload && on(xhr.upload, 'progress', this.progress);
['loadStart', 'load', 'loadEnd', 'abort'].forEach(type =>
on(xhr, type.toLowerCase(), this[type])
);
return this.beforeSend(env);
}
}).then(
xhr => {
this.complete(xhr);
if (chunks.length) {
upload(chunks.shift());
} else {
this.completeAll(xhr);
}
},
e => this.error(e)
);
};
upload(chunks.shift());
}
}
};
function match(pattern, path) {
return path.match(new RegExp(`^${pattern.replace(/\//g, '\\/').replace(/\*\*/g, '(\\/[^\\/]+)*').replace(/\*/g, '[^\\/]+').replace(/((?!\\))\?/g, '$1.')}$`, 'i'));
}
function chunk(files, size) {
const chunks = [];
for (let i = 0; i < files.length; i += size) {
const chunk = [];
for (let j = 0; j < size; j++) {
chunk.push(files[i + j]);
}
chunks.push(chunk);
}
return chunks;
}
function stop(e) {
e.preventDefault();
e.stopPropagation();
}

View File

@@ -0,0 +1,140 @@
import Class from '../mixin/class';
import {default as Togglable, toggleHeight} from '../mixin/togglable';
import {$, $$, attr, filter, getIndex, hasClass, includes, index, isInView, scrollIntoView, toggleClass, unwrap, wrapAll} from 'uikit-util';
export default {
mixins: [Class, Togglable],
props: {
targets: String,
active: null,
collapsible: Boolean,
multiple: Boolean,
toggle: String,
content: String,
transition: String,
offset: Number
},
data: {
targets: '> *',
active: false,
animation: [true],
collapsible: true,
multiple: false,
clsOpen: 'uk-open',
toggle: '> .uk-accordion-title',
content: '> .uk-accordion-content',
transition: 'ease',
offset: 0
},
computed: {
items: {
get({targets}, $el) {
return $$(targets, $el);
},
watch(items, prev) {
items.forEach(el => hide($(this.content, el), !hasClass(el, this.clsOpen)));
if (prev || hasClass(items, this.clsOpen)) {
return;
}
const active = this.active !== false && items[Number(this.active)]
|| !this.collapsible && items[0];
if (active) {
this.toggle(active, false);
}
},
immediate: true
},
toggles({toggle}) {
return this.items.map(item => $(toggle, item));
}
},
events: [
{
name: 'click',
delegate() {
return `${this.targets} ${this.$props.toggle}`;
},
handler(e) {
e.preventDefault();
this.toggle(index(this.toggles, e.current));
}
}
],
methods: {
toggle(item, animate) {
let items = [this.items[getIndex(item, this.items)]];
const activeItems = filter(this.items, `.${this.clsOpen}`);
if (!this.multiple && !includes(activeItems, items[0])) {
items = items.concat(activeItems);
}
if (!this.collapsible && activeItems.length < 2 && !filter(items, `:not(.${this.clsOpen})`).length) {
return;
}
items.forEach(el => this.toggleElement(el, !hasClass(el, this.clsOpen), (el, show) => {
toggleClass(el, this.clsOpen, show);
attr($(this.$props.toggle, el), 'aria-expanded', show);
const content = $(`${el._wrapper ? '> * ' : ''}${this.content}`, el);
if (animate === false || !this.hasTransition) {
hide(content, !show);
return;
}
if (!el._wrapper) {
el._wrapper = wrapAll(content, `<div${show ? ' hidden' : ''}>`);
}
hide(content, false);
return toggleHeight(this)(el._wrapper, show).then(() => {
hide(content, !show);
delete el._wrapper;
unwrap(content);
if (show) {
const toggle = $(this.$props.toggle, el);
if (!isInView(toggle)) {
scrollIntoView(toggle, {offset: this.offset});
}
}
});
}));
}
}
};
function hide(el, hide) {
el && (el.hidden = hide);
}

View File

@@ -0,0 +1,49 @@
import Class from '../mixin/class';
import Togglable from '../mixin/togglable';
import {assign} from 'uikit-util';
export default {
mixins: [Class, Togglable],
args: 'animation',
props: {
close: String
},
data: {
animation: [true],
selClose: '.uk-alert-close',
duration: 150,
hideProps: assign({opacity: 0}, Togglable.data.hideProps)
},
events: [
{
name: 'click',
delegate() {
return this.selClose;
},
handler(e) {
e.preventDefault();
this.close();
}
}
],
methods: {
close() {
this.toggleElement(this.$el).then(() => this.$destroy(true));
}
}
};

View File

@@ -0,0 +1,92 @@
import {css, fastdom, getEventPos, inBrowser, isTouch, on, once, parent, pointerCancel, pointerDown, pointerUp, toMs, trigger} from 'uikit-util';
export default function (UIkit) {
if (!inBrowser) {
return;
}
// throttle 'resize'
let pendingResize;
const handleResize = () => {
if (pendingResize) {
return;
}
pendingResize = true;
fastdom.write(() => pendingResize = false);
UIkit.update(null, 'resize');
};
on(window, 'load resize', handleResize);
on(document, 'loadedmetadata load', handleResize, true);
if ('ResizeObserver' in window) {
(new ResizeObserver(handleResize)).observe(document.documentElement);
}
// throttle `scroll` event (Safari triggers multiple `scroll` events per frame)
let pending;
on(window, 'scroll', e => {
if (pending) {
return;
}
pending = true;
fastdom.write(() => pending = false);
UIkit.update(null, e.type);
}, {passive: true, capture: true});
let started = 0;
on(document, 'animationstart', ({target}) => {
if ((css(target, 'animationName') || '').match(/^uk-.*(left|right)/)) {
started++;
css(document.documentElement, 'overflowX', 'hidden');
setTimeout(() => {
if (!--started) {
css(document.documentElement, 'overflowX', '');
}
}, toMs(css(target, 'animationDuration')) + 100);
}
}, true);
on(document, pointerDown, e => {
if (!isTouch(e)) {
return;
}
// Handle Swipe Gesture
const pos = getEventPos(e);
const target = 'tagName' in e.target ? e.target : parent(e.target);
once(document, `${pointerUp} ${pointerCancel} scroll`, e => {
const {x, y} = getEventPos(e);
// swipe
if (e.type !== 'scroll' && target && x && Math.abs(pos.x - x) > 100 || y && Math.abs(pos.y - y) > 100) {
setTimeout(() => {
trigger(target, 'swipe');
trigger(target, `swipe${swipeDirection(pos.x, pos.y, x, y)}`);
});
}
});
}, {passive: true});
}
function swipeDirection(x1, y1, x2, y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2)
? x1 - x2 > 0
? 'Left'
: 'Right'
: y1 - y2 > 0
? 'Up'
: 'Down';
}

View File

@@ -0,0 +1,57 @@
import Video from './video';
import {css, Dimensions, parent} from 'uikit-util';
export default {
mixins: [Video],
props: {
width: Number,
height: Number
},
data: {
automute: true
},
update: {
read() {
const el = this.$el;
const {offsetHeight: height, offsetWidth: width} = getPositionedParent(el) || parent(el);
const dim = Dimensions.cover(
{
width: this.width || el.naturalWidth || el.videoWidth || el.clientWidth,
height: this.height || el.naturalHeight || el.videoHeight || el.clientHeight
},
{
width: width + (width % 2 ? 1 : 0),
height: height + (height % 2 ? 1 : 0)
}
);
if (!dim.width || !dim.height) {
return false;
}
return dim;
},
write({height, width}) {
css(this.$el, {height, width});
},
events: ['resize']
}
};
function getPositionedParent(el) {
while ((el = parent(el))) {
if (css(el, 'position') !== 'static') {
return el;
}
}
}

View File

@@ -0,0 +1,387 @@
import Container from '../mixin/container';
import Position from '../mixin/position';
import Togglable from '../mixin/togglable';
import {addClass, append, apply, attr, css, hasClass, includes, isTouch, matches, MouseTracker, offset, on, once, parent, pointerCancel, pointerDown, pointerEnter, pointerLeave, pointerUp, query, removeClass, toggleClass, within} from 'uikit-util';
export let active;
export default {
mixins: [Container, Position, Togglable],
args: 'pos',
props: {
mode: 'list',
toggle: Boolean,
boundary: Boolean,
boundaryAlign: Boolean,
delayShow: Number,
delayHide: Number,
clsDrop: String
},
data: {
mode: ['click', 'hover'],
toggle: '- *',
boundary: true,
boundaryAlign: false,
delayShow: 0,
delayHide: 800,
clsDrop: false,
animation: ['uk-animation-fade'],
cls: 'uk-open',
container: false
},
computed: {
boundary({boundary}, $el) {
return boundary === true ? window : query(boundary, $el);
},
clsDrop({clsDrop}) {
return clsDrop || `uk-${this.$options.name}`;
},
clsPos() {
return this.clsDrop;
}
},
created() {
this.tracker = new MouseTracker();
},
connected() {
addClass(this.$el, this.clsDrop);
if (this.toggle && !this.target) {
this.target = this.$create('toggle', query(this.toggle, this.$el), {
target: this.$el,
mode: this.mode
}).$el;
attr(this.target, 'aria-haspopup', true);
}
},
disconnected() {
if (this.isActive()) {
active = null;
}
},
events: [
{
name: 'click',
delegate() {
return `.${this.clsDrop}-close`;
},
handler(e) {
e.preventDefault();
this.hide(false);
}
},
{
name: 'click',
delegate() {
return 'a[href^="#"]';
},
handler({defaultPrevented, current: {hash}}) {
if (!defaultPrevented && hash && !within(hash, this.$el)) {
this.hide(false);
}
}
},
{
name: 'beforescroll',
handler() {
this.hide(false);
}
},
{
name: 'toggle',
self: true,
handler(e, toggle) {
e.preventDefault();
if (this.isToggled()) {
this.hide(false);
} else {
this.show(toggle.$el, false);
}
}
},
{
name: 'toggleshow',
self: true,
handler(e, toggle) {
e.preventDefault();
this.show(toggle.$el);
}
},
{
name: 'togglehide',
self: true,
handler(e) {
e.preventDefault();
if (!matches(this.$el, ':focus,:hover')) {
this.hide();
}
}
},
{
name: `${pointerEnter} focusin`,
filter() {
return includes(this.mode, 'hover');
},
handler(e) {
if (!isTouch(e)) {
this.clearTimers();
}
}
},
{
name: `${pointerLeave} focusout`,
filter() {
return includes(this.mode, 'hover');
},
handler(e) {
if (!isTouch(e) && e.relatedTarget) {
this.hide();
}
}
},
{
name: 'toggled',
self: true,
handler(e, toggled) {
if (!toggled) {
return;
}
this.clearTimers();
this.position();
}
},
{
name: 'show',
self: true,
handler() {
active = this;
this.tracker.init();
once(this.$el, 'hide', on(document, pointerDown, ({target}) =>
!within(target, this.$el) && once(document, `${pointerUp} ${pointerCancel} scroll`, ({defaultPrevented, type, target: newTarget}) => {
if (!defaultPrevented && type === pointerUp && target === newTarget && !(this.target && within(target, this.target))) {
this.hide(false);
}
}, true)
), {self: true});
once(this.$el, 'hide', on(document, 'keydown', e => {
if (e.keyCode === 27) {
this.hide(false);
}
}), {self: true});
}
},
{
name: 'beforehide',
self: true,
handler() {
this.clearTimers();
}
},
{
name: 'hide',
handler({target}) {
if (this.$el !== target) {
active = active === null && within(target, this.$el) && this.isToggled() ? this : active;
return;
}
active = this.isActive() ? null : active;
this.tracker.cancel();
}
}
],
update: {
write() {
if (this.isToggled() && !hasClass(this.$el, this.clsEnter)) {
this.position();
}
},
events: ['resize']
},
methods: {
show(target = this.target, delay = true) {
if (this.isToggled() && target && this.target && target !== this.target) {
this.hide(false);
}
this.target = target;
this.clearTimers();
if (this.isActive()) {
return;
}
if (active) {
if (delay && active.isDelaying) {
this.showTimer = setTimeout(this.show, 10);
return;
}
let prev;
while (active && prev !== active && !within(this.$el, active.$el)) {
prev = active;
active.hide(false);
}
}
if (this.container && parent(this.$el) !== this.container) {
append(this.container, this.$el);
}
this.showTimer = setTimeout(() => this.toggleElement(this.$el, true), delay && this.delayShow || 0);
},
hide(delay = true) {
const hide = () => this.toggleElement(this.$el, false, false);
this.clearTimers();
this.isDelaying = getPositionedElements(this.$el).some(el => this.tracker.movesTo(el));
if (delay && this.isDelaying) {
this.hideTimer = setTimeout(this.hide, 50);
} else if (delay && this.delayHide) {
this.hideTimer = setTimeout(hide, this.delayHide);
} else {
hide();
}
},
clearTimers() {
clearTimeout(this.showTimer);
clearTimeout(this.hideTimer);
this.showTimer = null;
this.hideTimer = null;
this.isDelaying = false;
},
isActive() {
return active === this;
},
position() {
removeClass(this.$el, `${this.clsDrop}-stack`);
toggleClass(this.$el, `${this.clsDrop}-boundary`, this.boundaryAlign);
const boundary = offset(this.boundary);
const alignTo = this.boundaryAlign ? boundary : offset(this.target);
if (this.align === 'justify') {
const prop = this.getAxis() === 'y' ? 'width' : 'height';
css(this.$el, prop, alignTo[prop]);
} else if (this.boundary && this.$el.offsetWidth > Math.max(boundary.right - alignTo.left, alignTo.right - boundary.left)) {
addClass(this.$el, `${this.clsDrop}-stack`);
}
this.positionAt(this.$el, this.boundaryAlign ? this.boundary : this.target, this.boundary);
}
}
};
function getPositionedElements(el) {
const result = [];
apply(el, el => css(el, 'position') !== 'static' && result.push(el));
return result;
}

View File

@@ -0,0 +1,84 @@
import Class from '../mixin/class';
import {$, $$, closest, isInput, matches, parent, query, selInput} from 'uikit-util';
export default {
mixins: [Class],
args: 'target',
props: {
target: Boolean
},
data: {
target: false
},
computed: {
input(_, $el) {
return $(selInput, $el);
},
state() {
return this.input.nextElementSibling;
},
target({target}, $el) {
return target && (target === true
&& parent(this.input) === $el
&& this.input.nextElementSibling
|| query(target, $el));
}
},
update() {
const {target, input} = this;
if (!target) {
return;
}
let option;
const prop = isInput(target) ? 'value' : 'textContent';
const prev = target[prop];
const value = input.files && input.files[0]
? input.files[0].name
: matches(input, 'select') && (option = $$('option', input).filter(el => el.selected)[0]) // eslint-disable-line prefer-destructuring
? option.textContent
: input.value;
if (prev !== value) {
target[prop] = value;
}
},
events: [
{
name: 'change',
handler() {
this.$update();
}
},
{
name: 'reset',
el() {
return closest(this.$el, 'form');
},
handler() {
this.$update();
}
}
]
};

View File

@@ -0,0 +1,26 @@
import {isInView} from 'uikit-util';
// Deprecated
export default {
update: {
read(data) {
const inview = isInView(this.$el);
if (!inview || data.isInView === inview) {
return false;
}
data.isInView = inview;
},
write() {
this.$el.src = '' + this.$el.src; // force self-assign
},
events: ['scroll', 'resize']
}
};

View File

@@ -0,0 +1,159 @@
import Margin from './margin';
import Class from '../mixin/class';
import {addClass, children, css, height as getHeight, hasClass, scrolledOver, sortBy, toFloat, toggleClass} from 'uikit-util';
export default {
extends: Margin,
mixins: [Class],
name: 'grid',
props: {
masonry: Boolean,
parallax: Number
},
data: {
margin: 'uk-grid-margin',
clsStack: 'uk-grid-stack',
masonry: false,
parallax: 0
},
connected() {
this.masonry && addClass(this.$el, 'uk-flex-top uk-flex-wrap-top');
},
update: [
{
write({columns}) {
toggleClass(this.$el, this.clsStack, columns.length < 2);
},
events: ['resize']
},
{
read(data) {
let {columns, rows} = data;
// Filter component makes elements positioned absolute
if (!columns.length || !this.masonry && !this.parallax || positionedAbsolute(this.$el)) {
data.translates = false;
return false;
}
let translates = false;
const nodes = children(this.$el);
const columnHeights = getColumnHeights(columns);
const margin = getMarginTop(nodes, this.margin) * (rows.length - 1);
const elHeight = Math.max(...columnHeights) + margin;
if (this.masonry) {
columns = columns.map(column => sortBy(column, 'offsetTop'));
translates = getTranslates(rows, columns);
}
let padding = Math.abs(this.parallax);
if (padding) {
padding = columnHeights.reduce((newPadding, hgt, i) =>
Math.max(newPadding, hgt + margin + (i % 2 ? padding : padding / 8) - elHeight)
, 0);
}
return {padding, columns, translates, height: translates ? elHeight : ''};
},
write({height, padding}) {
css(this.$el, 'paddingBottom', padding || '');
height !== false && css(this.$el, 'height', height);
},
events: ['resize']
},
{
read({height}) {
if (positionedAbsolute(this.$el)) {
return false;
}
return {
scrolled: this.parallax
? scrolledOver(this.$el, height ? height - getHeight(this.$el) : 0) * Math.abs(this.parallax)
: false
};
},
write({columns, scrolled, translates}) {
if (scrolled === false && !translates) {
return;
}
columns.forEach((column, i) =>
column.forEach((el, j) =>
css(el, 'transform', !scrolled && !translates ? '' : `translateY(${
(translates && -translates[i][j]) + (scrolled ? i % 2 ? scrolled : scrolled / 8 : 0)
}px)`)
)
);
},
events: ['scroll', 'resize']
}
]
};
function positionedAbsolute(el) {
return children(el).some(el => css(el, 'position') === 'absolute');
}
function getTranslates(rows, columns) {
const rowHeights = rows.map(row =>
Math.max(...row.map(el => el.offsetHeight))
);
return columns.map(elements => {
let prev = 0;
return elements.map((element, row) =>
prev += row
? rowHeights[row - 1] - elements[row - 1].offsetHeight
: 0
);
});
}
function getMarginTop(nodes, cls) {
const [node] = nodes.filter(el => hasClass(el, cls));
return toFloat(node
? css(node, 'marginTop')
: css(nodes[0], 'paddingLeft'));
}
function getColumnHeights(columns) {
return columns.map(column =>
column.reduce((sum, el) => sum + el.offsetHeight, 0)
);
}

View File

@@ -0,0 +1,91 @@
import FlexBug from '../mixin/flex-bug';
import {getRows} from './margin';
import {$$, boxModelAdjust, css, dimensions, isVisible, toFloat} from 'uikit-util';
export default {
mixins: [FlexBug],
args: 'target',
props: {
target: String,
row: Boolean
},
data: {
target: '> *',
row: true,
forceHeight: true
},
computed: {
elements({target}, $el) {
return $$(target, $el);
}
},
update: {
read() {
return {
rows: (this.row ? getRows(this.elements) : [this.elements]).map(match)
};
},
write({rows}) {
rows.forEach(({heights, elements}) =>
elements.forEach((el, i) =>
css(el, 'minHeight', heights[i])
)
);
},
events: ['resize']
}
};
function match(elements) {
if (elements.length < 2) {
return {heights: [''], elements};
}
let heights = elements.map(getHeight);
let max = Math.max(...heights);
const hasMinHeight = elements.some(el => el.style.minHeight);
const hasShrunk = elements.some((el, i) => !el.style.minHeight && heights[i] < max);
if (hasMinHeight && hasShrunk) {
css(elements, 'minHeight', '');
heights = elements.map(getHeight);
max = Math.max(...heights);
}
heights = elements.map((el, i) =>
heights[i] === max && toFloat(el.style.minHeight).toFixed(2) !== max.toFixed(2) ? '' : max
);
return {heights, elements};
}
function getHeight(element) {
let style = false;
if (!isVisible(element)) {
style = element.style.display;
css(element, 'display', 'block', 'important');
}
const height = dimensions(element).height - boxModelAdjust(element, 'height', 'content-box');
if (style !== false) {
css(element, 'display', style);
}
return height;
}

View File

@@ -0,0 +1,92 @@
import FlexBug from '../mixin/flex-bug';
import {boxModelAdjust, css, dimensions, endsWith, height, isNumeric, isString, isVisible, offset, query, toFloat} from 'uikit-util';
export default {
mixins: [FlexBug],
props: {
expand: Boolean,
offsetTop: Boolean,
offsetBottom: Boolean,
minHeight: Number
},
data: {
expand: false,
offsetTop: false,
offsetBottom: false,
minHeight: 0
},
update: {
read({minHeight: prev}) {
if (!isVisible(this.$el)) {
return false;
}
let minHeight = '';
const box = boxModelAdjust(this.$el, 'height', 'content-box');
if (this.expand) {
minHeight = height(window) - (dimensions(document.documentElement).height - dimensions(this.$el).height) - box || '';
} else {
// on mobile devices (iOS and Android) window.innerHeight !== 100vh
minHeight = 'calc(100vh';
if (this.offsetTop) {
const {top} = offset(this.$el);
minHeight += top > 0 && top < height(window) / 2 ? ` - ${top}px` : '';
}
if (this.offsetBottom === true) {
minHeight += ` - ${dimensions(this.$el.nextElementSibling).height}px`;
} else if (isNumeric(this.offsetBottom)) {
minHeight += ` - ${this.offsetBottom}vh`;
} else if (this.offsetBottom && endsWith(this.offsetBottom, 'px')) {
minHeight += ` - ${toFloat(this.offsetBottom)}px`;
} else if (isString(this.offsetBottom)) {
minHeight += ` - ${dimensions(query(this.offsetBottom, this.$el)).height}px`;
}
minHeight += `${box ? ` - ${box}px` : ''})`;
}
return {minHeight, prev};
},
write({minHeight, prev}) {
css(this.$el, {minHeight});
if (minHeight !== prev) {
this.$update(this.$el, 'resize');
}
if (this.minHeight && toFloat(css(this.$el, 'minHeight')) < this.minHeight) {
css(this.$el, 'minHeight', this.minHeight);
}
},
events: ['resize']
}
};

View File

@@ -0,0 +1,191 @@
import SVG from './svg';
import closeIcon from '../../images/components/close-icon.svg';
import closeLarge from '../../images/components/close-large.svg';
import marker from '../../images/components/marker.svg';
import navbarToggleIcon from '../../images/components/navbar-toggle-icon.svg';
import overlayIcon from '../../images/components/overlay-icon.svg';
import paginationNext from '../../images/components/pagination-next.svg';
import paginationPrevious from '../../images/components/pagination-previous.svg';
import searchIcon from '../../images/components/search-icon.svg';
import searchLarge from '../../images/components/search-large.svg';
import searchNavbar from '../../images/components/search-navbar.svg';
import slidenavNext from '../../images/components/slidenav-next.svg';
import slidenavNextLarge from '../../images/components/slidenav-next-large.svg';
import slidenavPrevious from '../../images/components/slidenav-previous.svg';
import slidenavPreviousLarge from '../../images/components/slidenav-previous-large.svg';
import spinner from '../../images/components/spinner.svg';
import totop from '../../images/components/totop.svg';
import {$, addClass, apply, css, each, hasClass, hyphenate, isRtl, isString, parents, Promise, swap} from 'uikit-util';
const icons = {
spinner,
totop,
marker,
'close-icon': closeIcon,
'close-large': closeLarge,
'navbar-toggle-icon': navbarToggleIcon,
'overlay-icon': overlayIcon,
'pagination-next': paginationNext,
'pagination-previous': paginationPrevious,
'search-icon': searchIcon,
'search-large': searchLarge,
'search-navbar': searchNavbar,
'slidenav-next': slidenavNext,
'slidenav-next-large': slidenavNextLarge,
'slidenav-previous': slidenavPrevious,
'slidenav-previous-large': slidenavPreviousLarge
};
const Icon = {
install,
extends: SVG,
args: 'icon',
props: ['icon'],
data: {
include: ['focusable']
},
isIcon: true,
beforeConnect() {
addClass(this.$el, 'uk-icon');
},
methods: {
getSvg() {
const icon = getIcon(this.icon);
if (!icon) {
return Promise.reject('Icon not found.');
}
return Promise.resolve(icon);
}
}
};
export default Icon;
export const IconComponent = {
args: false,
extends: Icon,
data: vm => ({
icon: hyphenate(vm.constructor.options.name)
}),
beforeConnect() {
addClass(this.$el, this.$name);
}
};
export const Slidenav = {
extends: IconComponent,
beforeConnect() {
addClass(this.$el, 'uk-slidenav');
},
computed: {
icon({icon}, $el) {
return hasClass($el, 'uk-slidenav-large')
? `${icon}-large`
: icon;
}
}
};
export const Search = {
extends: IconComponent,
computed: {
icon({icon}, $el) {
return hasClass($el, 'uk-search-icon') && parents($el, '.uk-search-large').length
? 'search-large'
: parents($el, '.uk-search-navbar').length
? 'search-navbar'
: icon;
}
}
};
export const Close = {
extends: IconComponent,
computed: {
icon() {
return `close-${hasClass(this.$el, 'uk-close-large') ? 'large' : 'icon'}`;
}
}
};
export const Spinner = {
extends: IconComponent,
connected() {
this.svg.then(svg => svg && this.ratio !== 1 && css($('circle', svg), 'strokeWidth', 1 / this.ratio));
}
};
const parsed = {};
function install(UIkit) {
UIkit.icon.add = (name, svg) => {
const added = isString(name) ? ({[name]: svg}) : name;
each(added, (svg, name) => {
icons[name] = svg;
delete parsed[name];
});
if (UIkit._initialized) {
apply(document.body, el =>
each(UIkit.getComponents(el), cmp => {
cmp.$options.isIcon && cmp.icon in added && cmp.$reset();
})
);
}
};
}
function getIcon(icon) {
if (!icons[icon]) {
return null;
}
if (!parsed[icon]) {
parsed[icon] = $((icons[applyRtl(icon)] || icons[icon]).trim());
}
return parsed[icon].cloneNode(true);
}
function applyRtl(icon) {
return isRtl ? swap(swap(icon, 'left', 'right'), 'previous', 'next') : icon;
}

View File

@@ -0,0 +1,249 @@
import {createEvent, css, Dimensions, escape, getImage, includes, isUndefined, queryAll, startsWith, toFloat, toPx, trigger} from 'uikit-util';
export default {
args: 'dataSrc',
props: {
dataSrc: String,
dataSrcset: Boolean,
sizes: String,
width: Number,
height: Number,
offsetTop: String,
offsetLeft: String,
target: String
},
data: {
dataSrc: '',
dataSrcset: false,
sizes: false,
width: false,
height: false,
offsetTop: '50vh',
offsetLeft: '50vw',
target: false
},
computed: {
cacheKey({dataSrc}) {
return `${this.$name}.${dataSrc}`;
},
width({width, dataWidth}) {
return width || dataWidth;
},
height({height, dataHeight}) {
return height || dataHeight;
},
sizes({sizes, dataSizes}) {
return sizes || dataSizes;
},
isImg(_, $el) {
return isImg($el);
},
target: {
get({target}) {
return [this.$el, ...queryAll(target, this.$el)];
},
watch() {
this.observe();
}
},
offsetTop({offsetTop}) {
return toPx(offsetTop, 'height');
},
offsetLeft({offsetLeft}) {
return toPx(offsetLeft, 'width');
}
},
connected() {
if (!window.IntersectionObserver) {
setSrcAttrs(this.$el, this.dataSrc, this.dataSrcset, this.sizes);
return;
}
if (storage[this.cacheKey]) {
setSrcAttrs(this.$el, storage[this.cacheKey], this.dataSrcset, this.sizes);
} else if (this.isImg && this.width && this.height) {
setSrcAttrs(this.$el, getPlaceholderImage(this.width, this.height, this.sizes));
}
this.observer = new IntersectionObserver(this.load, {
rootMargin: `${this.offsetTop}px ${this.offsetLeft}px`
});
requestAnimationFrame(this.observe);
},
disconnected() {
this.observer && this.observer.disconnect();
},
update: {
read({image}) {
if (!this.observer) {
return false;
}
if (!image && document.readyState === 'complete') {
this.load(this.observer.takeRecords());
}
if (this.isImg) {
return false;
}
image && image.then(img => img && img.currentSrc !== '' && setSrcAttrs(this.$el, currentSrc(img)));
},
write(data) {
if (this.dataSrcset && window.devicePixelRatio !== 1) {
const bgSize = css(this.$el, 'backgroundSize');
if (bgSize.match(/^(auto\s?)+$/) || toFloat(bgSize) === data.bgSize) {
data.bgSize = getSourceSize(this.dataSrcset, this.sizes);
css(this.$el, 'backgroundSize', `${data.bgSize}px`);
}
}
},
events: ['resize']
},
methods: {
load(entries) {
// Old chromium based browsers (UC Browser) did not implement `isIntersecting`
if (!entries.some(entry => isUndefined(entry.isIntersecting) || entry.isIntersecting)) {
return;
}
this._data.image = getImage(this.dataSrc, this.dataSrcset, this.sizes).then(img => {
setSrcAttrs(this.$el, currentSrc(img), img.srcset, img.sizes);
storage[this.cacheKey] = currentSrc(img);
return img;
}, e => trigger(this.$el, new e.constructor(e.type, e)));
this.observer.disconnect();
},
observe() {
if (this._connected && !this._data.image) {
this.target.forEach(el => this.observer.observe(el));
}
}
}
};
function setSrcAttrs(el, src, srcset, sizes) {
if (isImg(el)) {
const set = (prop, val) => val && val !== el[prop] && (el[prop] = val);
set('sizes', sizes);
set('srcset', srcset);
set('src', src);
} else if (src) {
const change = !includes(el.style.backgroundImage, src);
if (change) {
css(el, 'backgroundImage', `url(${escape(src)})`);
trigger(el, createEvent('load', false));
}
}
}
function getPlaceholderImage(width, height, sizes) {
if (sizes) {
({width, height} = Dimensions.ratio({width, height}, 'width', toPx(sizesToPixel(sizes))));
}
return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`;
}
const sizesRe = /\s*(.*?)\s*(\w+|calc\(.*?\))\s*(?:,|$)/g;
function sizesToPixel(sizes) {
let matches;
sizesRe.lastIndex = 0;
while ((matches = sizesRe.exec(sizes))) {
if (!matches[1] || window.matchMedia(matches[1]).matches) {
matches = evaluateSize(matches[2]);
break;
}
}
return matches || '100vw';
}
const sizeRe = /\d+(?:\w+|%)/g;
const additionRe = /[+-]?(\d+)/g;
function evaluateSize(size) {
return startsWith(size, 'calc')
? size
.slice(5, -1)
.replace(sizeRe, size => toPx(size))
.replace(/ /g, '')
.match(additionRe)
.reduce((a, b) => a + +b, 0)
: size;
}
const srcSetRe = /\s+\d+w\s*(?:,|$)/g;
function getSourceSize(srcset, sizes) {
const srcSize = toPx(sizesToPixel(sizes));
const descriptors = (srcset.match(srcSetRe) || []).map(toFloat).sort((a, b) => a - b);
return descriptors.filter(size => size >= srcSize)[0] || descriptors.pop() || '';
}
function isImg(el) {
return el.tagName === 'IMG';
}
function currentSrc(el) {
return el.currentSrc || el.src;
}
const key = '__test__';
let storage;
// workaround for Safari's private browsing mode and accessing sessionStorage in Blink
try {
storage = window.sessionStorage || {};
storage[key] = 1;
delete storage[key];
} catch (e) {
storage = {};
}

View File

@@ -0,0 +1,41 @@
export {default as Accordion} from './accordion';
export {default as Alert} from './alert';
export {default as Cover} from './cover';
export {default as Drop, default as Dropdown} from './drop';
export {default as FormCustom} from './form-custom';
export {default as Gif} from './gif';
export {default as Grid} from './grid';
export {default as HeightMatch} from './height-match';
export {default as HeightViewport} from './height-viewport';
export {default as Icon} from './icon';
export {default as Img} from './img';
export {default as Leader} from './leader';
export {default as Margin} from './margin';
export {default as Modal} from './modal';
export {default as Nav} from './nav';
export {default as Navbar} from './navbar';
export {default as Offcanvas} from './offcanvas';
export {default as OverflowAuto} from './overflow-auto';
export {default as Responsive} from './responsive';
export {default as Scroll} from './scroll';
export {default as Scrollspy} from './scrollspy';
export {default as ScrollspyNav} from './scrollspy-nav';
export {default as Sticky} from './sticky';
export {default as Svg} from './svg';
export {default as Switcher} from './switcher';
export {default as Tab} from './tab';
export {default as Toggle} from './toggle';
export {default as Video} from './video';
// Icon components
export {Close} from './icon';
export {Spinner} from './icon';
export {Slidenav as SlidenavNext} from './icon';
export {Slidenav as SlidenavPrevious} from './icon';
export {Search as SearchIcon} from './icon';
export {IconComponent as Marker} from './icon';
export {IconComponent as NavbarToggleIcon} from './icon';
export {IconComponent as OverlayIcon} from './icon';
export {IconComponent as PaginationNext} from './icon';
export {IconComponent as PaginationPrevious} from './icon';
export {IconComponent as Totop} from './icon';

View File

@@ -0,0 +1,67 @@
import Class from '../mixin/class';
import Media from '../mixin/media';
import {attr, getCssVar, toggleClass, unwrap, wrapInner} from 'uikit-util';
export default {
mixins: [Class, Media],
props: {
fill: String
},
data: {
fill: '',
clsWrapper: 'uk-leader-fill',
clsHide: 'uk-leader-hide',
attrFill: 'data-fill'
},
computed: {
fill({fill}) {
return fill || getCssVar('leader-fill-content');
}
},
connected() {
[this.wrapper] = wrapInner(this.$el, `<span class="${this.clsWrapper}">`);
},
disconnected() {
unwrap(this.wrapper.childNodes);
},
update: {
read({changed, width}) {
const prev = width;
width = Math.floor(this.$el.offsetWidth / 2);
return {
width,
fill: this.fill,
changed: changed || prev !== width,
hide: !this.matchMedia
};
},
write(data) {
toggleClass(this.wrapper, this.clsHide, data.hide);
if (data.changed) {
data.changed = false;
attr(this.wrapper, this.attrFill, new Array(data.width).join(data.fill));
}
},
events: ['resize']
}
};

View File

@@ -0,0 +1,129 @@
import {isRtl, isVisible, offsetPosition, toggleClass} from 'uikit-util';
export default {
props: {
margin: String,
firstColumn: Boolean
},
data: {
margin: 'uk-margin-small-top',
firstColumn: 'uk-first-column'
},
update: {
read() {
const rows = getRows(this.$el.children);
return {
rows,
columns: getColumns(rows)
};
},
write({columns, rows}) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].length; j++) {
toggleClass(rows[i][j], this.margin, i !== 0);
toggleClass(rows[i][j], this.firstColumn, !!~columns[0].indexOf(rows[i][j]));
}
}
},
events: ['resize']
}
};
export function getRows(items) {
return sortBy(items, 'top', 'bottom');
}
function getColumns(rows) {
const columns = [];
for (let i = 0; i < rows.length; i++) {
const sorted = sortBy(rows[i], 'left', 'right');
for (let j = 0; j < sorted.length; j++) {
columns[j] = !columns[j] ? sorted[j] : columns[j].concat(sorted[j]);
}
}
return isRtl
? columns.reverse()
: columns;
}
function sortBy(items, startProp, endProp) {
const sorted = [[]];
for (let i = 0; i < items.length; i++) {
const el = items[i];
if (!isVisible(el)) {
continue;
}
let dim = getOffset(el);
for (let j = sorted.length - 1; j >= 0; j--) {
const current = sorted[j];
if (!current[0]) {
current.push(el);
break;
}
let startDim;
if (current[0].offsetParent === el.offsetParent) {
startDim = getOffset(current[0]);
} else {
dim = getOffset(el, true);
startDim = getOffset(current[0], true);
}
if (dim[startProp] >= startDim[endProp] - 1 && dim[startProp] !== startDim[startProp]) {
sorted.push([el]);
break;
}
if (dim[endProp] - 1 > startDim[startProp] || dim[startProp] === startDim[startProp]) {
current.push(el);
break;
}
if (j === 0) {
sorted.unshift([el]);
break;
}
}
}
return sorted;
}
function getOffset(element, offset = false) {
let {offsetTop, offsetLeft, offsetHeight, offsetWidth} = element;
if (offset) {
[offsetTop, offsetLeft] = offsetPosition(element);
}
return {
top: offsetTop,
left: offsetLeft,
bottom: offsetTop + offsetHeight,
right: offsetLeft + offsetWidth
};
}

View File

@@ -0,0 +1,145 @@
import Modal from '../mixin/modal';
import {$, addClass, assign, css, Deferred, hasClass, height, html, isString, on, Promise, removeClass} from 'uikit-util';
export default {
install,
mixins: [Modal],
data: {
clsPage: 'uk-modal-page',
selPanel: '.uk-modal-dialog',
selClose: '.uk-modal-close, .uk-modal-close-default, .uk-modal-close-outside, .uk-modal-close-full'
},
events: [
{
name: 'show',
self: true,
handler() {
if (hasClass(this.panel, 'uk-margin-auto-vertical')) {
addClass(this.$el, 'uk-flex');
} else {
css(this.$el, 'display', 'block');
}
height(this.$el); // force reflow
}
},
{
name: 'hidden',
self: true,
handler() {
css(this.$el, 'display', '');
removeClass(this.$el, 'uk-flex');
}
}
]
};
function install({modal}) {
modal.dialog = function (content, options) {
const dialog = modal(
`<div class="uk-modal">
<div class="uk-modal-dialog">${content}</div>
</div>`,
options
);
dialog.show();
on(dialog.$el, 'hidden', () =>
Promise.resolve().then(() =>
dialog.$destroy(true)
), {self: true}
);
return dialog;
};
modal.alert = function (message, options) {
return openDialog(
({labels}) => `<div class="uk-modal-body">${isString(message) ? message : html(message)}</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-primary uk-modal-close" autofocus>${labels.ok}</button>
</div>`,
options,
deferred => deferred.resolve()
);
};
modal.confirm = function (message, options) {
return openDialog(
({labels}) => `<form>
<div class="uk-modal-body">${isString(message) ? message : html(message)}</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">${labels.cancel}</button>
<button class="uk-button uk-button-primary" autofocus>${labels.ok}</button>
</div>
</form>`,
options,
deferred => deferred.reject()
);
};
modal.prompt = function (message, value, options) {
return openDialog(
({labels}) => `<form class="uk-form-stacked">
<div class="uk-modal-body">
<label>${isString(message) ? message : html(message)}</label>
<input class="uk-input" value="${value || ''}" autofocus>
</div>
<div class="uk-modal-footer uk-text-right">
<button class="uk-button uk-button-default uk-modal-close" type="button">${labels.cancel}</button>
<button class="uk-button uk-button-primary">${labels.ok}</button>
</div>
</form>`,
options,
deferred => deferred.resolve(null),
dialog => $('input', dialog.$el).value
);
};
modal.labels = {
ok: 'Ok',
cancel: 'Cancel'
};
function openDialog(tmpl, options, hideFn, submitFn) {
options = assign({bgClose: false, escClose: true, labels: modal.labels}, options);
const dialog = modal.dialog(tmpl(options), options);
const deferred = new Deferred();
let resolved = false;
on(dialog.$el, 'submit', 'form', e => {
e.preventDefault();
deferred.resolve(submitFn && submitFn(dialog));
resolved = true;
dialog.hide();
});
on(dialog.$el, 'hide', () => !resolved && hideFn(deferred));
deferred.promise.dialog = dialog;
return deferred.promise;
}
}

View File

@@ -0,0 +1,13 @@
import Accordion from './accordion';
export default {
extends: Accordion,
data: {
targets: '> .uk-parent',
toggle: '> a',
content: '> ul'
}
};

View File

@@ -0,0 +1,413 @@
import {active} from './drop';
import Class from '../mixin/class';
import FlexBug from '../mixin/flex-bug';
import Container from '../mixin/container';
import {$, $$, addClass, after, assign, css, findIndex, hasAttr, hasClass, height, includes, isRtl, isVisible, matches, noop, once, parent, Promise, query, remove, selFocusable, toFloat, Transition, within} from 'uikit-util';
const navItem = '.uk-navbar-nav > li > a, .uk-navbar-item, .uk-navbar-toggle';
export default {
mixins: [Class, Container, FlexBug],
props: {
dropdown: String,
mode: 'list',
align: String,
offset: Number,
boundary: Boolean,
boundaryAlign: Boolean,
clsDrop: String,
delayShow: Number,
delayHide: Number,
dropbar: Boolean,
dropbarMode: String,
dropbarAnchor: Boolean,
duration: Number
},
data: {
dropdown: navItem,
align: isRtl ? 'right' : 'left',
clsDrop: 'uk-navbar-dropdown',
mode: undefined,
offset: undefined,
delayShow: undefined,
delayHide: undefined,
boundaryAlign: undefined,
flip: 'x',
boundary: true,
dropbar: false,
dropbarMode: 'slide',
dropbarAnchor: false,
duration: 200,
forceHeight: true,
selMinHeight: navItem,
container: false
},
computed: {
boundary({boundary, boundaryAlign}, $el) {
return boundary === true || boundaryAlign ? $el : boundary;
},
dropbarAnchor({dropbarAnchor}, $el) {
return query(dropbarAnchor, $el);
},
pos({align}) {
return `bottom-${align}`;
},
dropbar: {
get({dropbar}) {
if (!dropbar) {
return null;
}
dropbar = this._dropbar || query(dropbar, this.$el) || $('+ .uk-navbar-dropbar', this.$el);
return dropbar ? dropbar : (this._dropbar = $('<div></div>'));
},
watch(dropbar) {
addClass(dropbar, 'uk-navbar-dropbar');
},
immediate: true
},
dropContainer(_, $el) {
return this.container || $el;
},
dropdowns: {
get({clsDrop}, $el) {
const dropdowns = $$(`.${clsDrop}`, $el);
if (this.dropContainer !== $el) {
$$(`.${clsDrop}`, this.dropContainer).forEach(el => {
const dropdown = this.getDropdown(el);
if (!includes(dropdowns, el) && dropdown && dropdown.target && within(dropdown.target, this.$el)) {
dropdowns.push(el);
}
});
}
return dropdowns;
},
watch(dropdowns) {
this.$create(
'drop',
dropdowns.filter(el => !this.getDropdown(el)),
assign({}, this.$props, {boundary: this.boundary, pos: this.pos, offset: this.dropbar || this.offset})
);
},
immediate: true
},
toggles({dropdown}, $el) {
return $$(dropdown, $el);
}
},
disconnected() {
this.dropbar && remove(this.dropbar);
delete this._dropbar;
},
events: [
{
name: 'mouseover focusin',
delegate() {
return this.dropdown;
},
handler({current}) {
const active = this.getActive();
if (active && includes(active.mode, 'hover') && active.target && !within(active.target, current) && !active.tracker.movesTo(active.$el)) {
active.hide(false);
}
}
},
{
name: 'keydown',
delegate() {
return this.dropdown;
},
handler(e) {
const {current, keyCode} = e;
const active = this.getActive();
if (keyCode === keyMap.DOWN && hasAttr(current, 'aria-expanded')) {
e.preventDefault();
if (!active || active.target !== current) {
current.click();
once(this.dropContainer, 'show', ({target}) => focusFirstFocusableElement(target));
} else {
focusFirstFocusableElement(active.$el);
}
}
handleNavItemNavigation(e, this.toggles, active);
}
},
{
name: 'keydown',
el() {
return this.dropContainer;
},
delegate() {
return `.${this.clsDrop}`;
},
handler(e) {
const {current, keyCode} = e;
if (!includes(this.dropdowns, current)) {
return;
}
const active = this.getActive();
const elements = $$(selFocusable, current);
const i = findIndex(elements, el => matches(el, ':focus'));
if (keyCode === keyMap.UP) {
e.preventDefault();
if (i > 0) {
elements[i - 1].focus();
}
}
if (keyCode === keyMap.DOWN) {
e.preventDefault();
if (i < elements.length - 1) {
elements[i + 1].focus();
}
}
if (keyCode === keyMap.ESC) {
active && active.target && active.target.focus();
}
handleNavItemNavigation(e, this.toggles, active);
}
},
{
name: 'mouseleave',
el() {
return this.dropbar;
},
filter() {
return this.dropbar;
},
handler() {
const active = this.getActive();
if (active && includes(active.mode, 'hover') && !this.dropdowns.some(el => matches(el, ':hover'))) {
active.hide();
}
}
},
{
name: 'beforeshow',
el() {
return this.dropContainer;
},
filter() {
return this.dropbar;
},
handler() {
if (!parent(this.dropbar)) {
after(this.dropbarAnchor || this.$el, this.dropbar);
}
}
},
{
name: 'show',
el() {
return this.dropContainer;
},
filter() {
return this.dropbar;
},
handler(_, {$el, dir}) {
if (!hasClass($el, this.clsDrop)) {
return;
}
if (this.dropbarMode === 'slide') {
addClass(this.dropbar, 'uk-navbar-dropbar-slide');
}
this.clsDrop && addClass($el, `${this.clsDrop}-dropbar`);
if (dir === 'bottom') {
this.transitionTo($el.offsetHeight + toFloat(css($el, 'marginTop')) + toFloat(css($el, 'marginBottom')), $el);
}
}
},
{
name: 'beforehide',
el() {
return this.dropContainer;
},
filter() {
return this.dropbar;
},
handler(e, {$el}) {
const active = this.getActive();
if (matches(this.dropbar, ':hover') && active && active.$el === $el) {
e.preventDefault();
}
}
},
{
name: 'hide',
el() {
return this.dropContainer;
},
filter() {
return this.dropbar;
},
handler(_, {$el}) {
if (!hasClass($el, this.clsDrop)) {
return;
}
const active = this.getActive();
if (!active || active && active.$el === $el) {
this.transitionTo(0);
}
}
}
],
methods: {
getActive() {
return active && within(active.target, this.$el) && active;
},
transitionTo(newHeight, el) {
const {dropbar} = this;
const oldHeight = isVisible(dropbar) ? height(dropbar) : 0;
el = oldHeight < newHeight && el;
css(el, 'clip', `rect(0,${el.offsetWidth}px,${oldHeight}px,0)`);
height(dropbar, oldHeight);
Transition.cancel([el, dropbar]);
return Promise.all([
Transition.start(dropbar, {height: newHeight}, this.duration),
Transition.start(el, {clip: `rect(0,${el.offsetWidth}px,${newHeight}px,0)`}, this.duration)
])
.catch(noop)
.then(() => {
css(el, {clip: ''});
this.$update(dropbar);
});
},
getDropdown(el) {
return this.$getComponent(el, 'drop') || this.$getComponent(el, 'dropdown');
}
}
};
function handleNavItemNavigation(e, toggles, active) {
const {current, keyCode} = e;
const target = active && active.target || current;
const i = toggles.indexOf(target);
// Left
if (keyCode === keyMap.LEFT && i > 0) {
active && active.hide(false);
toggles[i - 1].focus();
}
// Right
if (keyCode === keyMap.RIGHT && i < toggles.length - 1) {
active && active.hide(false);
toggles[i + 1].focus();
}
if (keyCode === keyMap.TAB) {
target.focus();
active && active.hide(false);
}
}
function focusFirstFocusableElement(el) {
if (!$(':focus', el)) {
const focusEl = $(selFocusable, el);
if (focusEl) {
focusEl.focus();
}
}
}
const keyMap = {
TAB: 9,
ESC: 27,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};

View File

@@ -0,0 +1,244 @@
import Modal from '../mixin/modal';
import {$, addClass, append, css, endsWith, hasClass, height, isVisible, parent, removeClass, unwrap, wrapAll} from 'uikit-util';
export default {
mixins: [Modal],
args: 'mode',
props: {
mode: String,
flip: Boolean,
overlay: Boolean
},
data: {
mode: 'slide',
flip: false,
overlay: false,
clsPage: 'uk-offcanvas-page',
clsContainer: 'uk-offcanvas-container',
selPanel: '.uk-offcanvas-bar',
clsFlip: 'uk-offcanvas-flip',
clsContainerAnimation: 'uk-offcanvas-container-animation',
clsSidebarAnimation: 'uk-offcanvas-bar-animation',
clsMode: 'uk-offcanvas',
clsOverlay: 'uk-offcanvas-overlay',
selClose: '.uk-offcanvas-close',
container: false
},
computed: {
clsFlip({flip, clsFlip}) {
return flip ? clsFlip : '';
},
clsOverlay({overlay, clsOverlay}) {
return overlay ? clsOverlay : '';
},
clsMode({mode, clsMode}) {
return `${clsMode}-${mode}`;
},
clsSidebarAnimation({mode, clsSidebarAnimation}) {
return mode === 'none' || mode === 'reveal' ? '' : clsSidebarAnimation;
},
clsContainerAnimation({mode, clsContainerAnimation}) {
return mode !== 'push' && mode !== 'reveal' ? '' : clsContainerAnimation;
},
transitionElement({mode}) {
return mode === 'reveal' ? parent(this.panel) : this.panel;
}
},
update: {
read() {
if (this.isToggled() && !isVisible(this.$el)) {
this.hide();
}
},
events: ['resize']
},
events: [
{
name: 'click',
delegate() {
return 'a[href^="#"]';
},
handler({current: {hash}, defaultPrevented}) {
if (!defaultPrevented && hash && $(hash, document.body)) {
this.hide();
}
}
},
{
name: 'touchstart',
passive: true,
el() {
return this.panel;
},
handler({targetTouches}) {
if (targetTouches.length === 1) {
this.clientY = targetTouches[0].clientY;
}
}
},
{
name: 'touchmove',
self: true,
passive: false,
filter() {
return this.overlay;
},
handler(e) {
e.cancelable && e.preventDefault();
}
},
{
name: 'touchmove',
passive: false,
el() {
return this.panel;
},
handler(e) {
if (e.targetTouches.length !== 1) {
return;
}
const clientY = e.targetTouches[0].clientY - this.clientY;
const {scrollTop, scrollHeight, clientHeight} = this.panel;
if (clientHeight >= scrollHeight
|| scrollTop === 0 && clientY > 0
|| scrollHeight - scrollTop <= clientHeight && clientY < 0
) {
e.cancelable && e.preventDefault();
}
}
},
{
name: 'show',
self: true,
handler() {
if (this.mode === 'reveal' && !hasClass(parent(this.panel), this.clsMode)) {
wrapAll(this.panel, '<div>');
addClass(parent(this.panel), this.clsMode);
}
css(document.documentElement, 'overflowY', this.overlay ? 'hidden' : '');
addClass(document.body, this.clsContainer, this.clsFlip);
css(document.body, 'touch-action', 'pan-y pinch-zoom');
css(this.$el, 'display', 'block');
addClass(this.$el, this.clsOverlay);
addClass(this.panel, this.clsSidebarAnimation, this.mode !== 'reveal' ? this.clsMode : '');
height(document.body); // force reflow
addClass(document.body, this.clsContainerAnimation);
this.clsContainerAnimation && suppressUserScale();
}
},
{
name: 'hide',
self: true,
handler() {
removeClass(document.body, this.clsContainerAnimation);
css(document.body, 'touch-action', '');
}
},
{
name: 'hidden',
self: true,
handler() {
this.clsContainerAnimation && resumeUserScale();
if (this.mode === 'reveal') {
unwrap(this.panel);
}
removeClass(this.panel, this.clsSidebarAnimation, this.clsMode);
removeClass(this.$el, this.clsOverlay);
css(this.$el, 'display', '');
removeClass(document.body, this.clsContainer, this.clsFlip);
css(document.documentElement, 'overflowY', '');
}
},
{
name: 'swipeLeft swipeRight',
handler(e) {
if (this.isToggled() && endsWith(e.type, 'Left') ^ this.flip) {
this.hide();
}
}
}
]
};
// Chrome in responsive mode zooms page upon opening offcanvas
function suppressUserScale() {
getViewport().content += ',user-scalable=0';
}
function resumeUserScale() {
const viewport = getViewport();
viewport.content = viewport.content.replace(/,user-scalable=0$/, '');
}
function getViewport() {
return $('meta[name="viewport"]', document.head) || append(document.head, '<meta name="viewport">');
}

View File

@@ -0,0 +1,61 @@
import Class from '../mixin/class';
import {closest, css, dimensions, height, isVisible, toFloat, trigger} from 'uikit-util';
export default {
mixins: [Class],
props: {
selContainer: String,
selContent: String,
minHeight: Number
},
data: {
selContainer: '.uk-modal',
selContent: '.uk-modal-dialog',
minHeight: 150
},
computed: {
container({selContainer}, $el) {
return closest($el, selContainer);
},
content({selContent}, $el) {
return closest($el, selContent);
}
},
connected() {
css(this.$el, 'minHeight', this.minHeight);
},
update: {
read() {
if (!this.content || !this.container || !isVisible(this.$el)) {
return false;
}
return {
current: toFloat(css(this.$el, 'maxHeight')),
max: Math.max(this.minHeight, height(this.container) - (dimensions(this.content).height - height(this.$el)))
};
},
write({current, max}) {
css(this.$el, 'maxHeight', max);
if (Math.round(current) !== Math.round(max)) {
trigger(this.$el, 'resize');
}
},
events: ['resize']
}
};

View File

@@ -0,0 +1,30 @@
import {addClass, Dimensions, height, isVisible, parent, width} from 'uikit-util';
export default {
props: ['width', 'height'],
connected() {
addClass(this.$el, 'uk-responsive-width');
},
update: {
read() {
return isVisible(this.$el) && this.width && this.height
? {width: width(parent(this.$el)), height: this.height}
: false;
},
write(dim) {
height(this.$el, Dimensions.contain({
height: this.height,
width: this.width
}, dim).height);
},
events: ['resize']
}
};

View File

@@ -0,0 +1,43 @@
import {$, escape, scrollIntoView, trigger} from 'uikit-util';
export default {
props: {
offset: Number
},
data: {
offset: 0
},
methods: {
scrollTo(el) {
el = el && $(el) || document.body;
if (trigger(this.$el, 'beforescroll', [this, el])) {
scrollIntoView(el, {offset: this.offset}).then(() =>
trigger(this.$el, 'scrolled', [this, el])
);
}
}
},
events: {
click(e) {
if (e.defaultPrevented) {
return;
}
e.preventDefault();
this.scrollTo(`#${escape(decodeURIComponent((this.$el.hash || '').substr(1)))}`);
}
}
};

View File

@@ -0,0 +1,104 @@
import {$$, addClass, closest, escape, getViewport, getViewportClientHeight, hasClass, isVisible, offset, removeClass, scrollParents, trigger} from 'uikit-util';
export default {
props: {
cls: String,
closest: String,
scroll: Boolean,
overflow: Boolean,
offset: Number
},
data: {
cls: 'uk-active',
closest: false,
scroll: false,
overflow: true,
offset: 0
},
computed: {
links: {
get(_, $el) {
return $$('a[href^="#"]', $el).filter(el => el.hash);
},
watch(links) {
if (this.scroll) {
this.$create('scroll', links, {offset: this.offset || 0});
}
},
immediate: true
},
targets() {
return $$(this.links.map(el => escape(el.hash).substr(1)).join(','));
},
elements({closest: selector}) {
return closest(this.links, selector || '*');
}
},
update: [
{
read() {
const {length} = this.targets;
if (!length || !isVisible(this.$el)) {
return false;
}
const [scrollElement] = scrollParents(this.targets, /auto|scroll/, true);
const {scrollTop, scrollHeight} = scrollElement;
const max = scrollHeight - getViewportClientHeight(scrollElement);
let active = false;
if (scrollTop === max) {
active = length - 1;
} else {
this.targets.every((el, i) => {
if (offset(el).top - offset(getViewport(scrollElement)).top - this.offset <= 0) {
active = i;
return true;
}
});
if (active === false && this.overflow) {
active = 0;
}
}
return {active};
},
write({active}) {
const changed = active !== false && !hasClass(this.elements[active], this.cls);
this.links.forEach(el => el.blur());
removeClass(this.elements, this.cls);
addClass(this.elements[active], this.cls);
if (changed) {
trigger(this.$el, 'active', [active, this.elements[active]]);
}
},
events: ['scroll', 'resize']
}
]
};

View File

@@ -0,0 +1,150 @@
import {$$, css, filter, data as getData, isInView, once, Promise, removeClass, removeClasses, toggleClass, trigger} from 'uikit-util';
const stateKey = '_ukScrollspy';
export default {
args: 'cls',
props: {
cls: String,
target: String,
hidden: Boolean,
offsetTop: Number,
offsetLeft: Number,
repeat: Boolean,
delay: Number
},
data: () => ({
cls: '',
target: false,
hidden: true,
offsetTop: 0,
offsetLeft: 0,
repeat: false,
delay: 0,
inViewClass: 'uk-scrollspy-inview'
}),
computed: {
elements: {
get({target}, $el) {
return target ? $$(target, $el) : [$el];
},
watch(elements) {
if (this.hidden) {
css(filter(elements, `:not(.${this.inViewClass})`), 'visibility', 'hidden');
}
},
immediate: true
}
},
disconnected() {
this.elements.forEach(el => {
removeClass(el, this.inViewClass, el[stateKey] ? el[stateKey].cls : '');
delete el[stateKey];
});
},
update: [
{
read(data) {
// Let child components be applied at least once first
if (!data.update) {
Promise.resolve().then(() => {
this.$emit();
data.update = true;
});
return false;
}
this.elements.forEach(el => {
if (!el[stateKey]) {
el[stateKey] = {cls: getData(el, 'uk-scrollspy-class') || this.cls};
}
el[stateKey].show = isInView(el, this.offsetTop, this.offsetLeft);
});
},
write(data) {
this.elements.forEach(el => {
const state = el[stateKey];
if (state.show && !state.inview && !state.queued) {
state.queued = true;
data.promise = (data.promise || Promise.resolve()).then(() =>
new Promise(resolve =>
setTimeout(resolve, this.delay)
)
).then(() => {
this.toggle(el, true);
setTimeout(() => {
state.queued = false;
this.$emit();
}, 300);
});
} else if (!state.show && state.inview && !state.queued && this.repeat) {
this.toggle(el, false);
}
});
},
events: ['scroll', 'resize']
}
],
methods: {
toggle(el, inview) {
const state = el[stateKey];
state.off && state.off();
css(el, 'visibility', !inview && this.hidden ? 'hidden' : '');
toggleClass(el, this.inViewClass, inview);
toggleClass(el, state.cls);
if (/\buk-animation-/.test(state.cls)) {
state.off = once(el, 'animationcancel animationend', () =>
removeClasses(el, 'uk-animation-[\\w-]+')
);
}
trigger(el, inview ? 'inview' : 'outview');
state.inview = inview;
this.$update(el);
}
}
};

View File

@@ -0,0 +1,335 @@
import Class from '../mixin/class';
import Media from '../mixin/media';
import {$, addClass, after, Animation, assign, css, dimensions, fastdom, height as getHeight, hasClass, isNumeric, isString, isVisible, noop, offset, offsetPosition, parent, query, remove, removeClass, replaceClass, scrollTop, toFloat, toggleClass, toPx, trigger, within} from 'uikit-util';
export default {
mixins: [Class, Media],
props: {
top: null,
bottom: Boolean,
offset: String,
animation: String,
clsActive: String,
clsInactive: String,
clsFixed: String,
clsBelow: String,
selTarget: String,
widthElement: Boolean,
showOnUp: Boolean,
targetOffset: Number
},
data: {
top: 0,
bottom: false,
offset: 0,
animation: '',
clsActive: 'uk-active',
clsInactive: '',
clsFixed: 'uk-sticky-fixed',
clsBelow: 'uk-sticky-below',
selTarget: '',
widthElement: false,
showOnUp: false,
targetOffset: false
},
computed: {
offset({offset}) {
return toPx(offset);
},
selTarget({selTarget}, $el) {
return selTarget && $(selTarget, $el) || $el;
},
widthElement({widthElement}, $el) {
return query(widthElement, $el) || this.placeholder;
},
isActive: {
get() {
return hasClass(this.selTarget, this.clsActive);
},
set(value) {
if (value && !this.isActive) {
replaceClass(this.selTarget, this.clsInactive, this.clsActive);
trigger(this.$el, 'active');
} else if (!value && !hasClass(this.selTarget, this.clsInactive)) {
replaceClass(this.selTarget, this.clsActive, this.clsInactive);
trigger(this.$el, 'inactive');
}
}
}
},
connected() {
this.placeholder = $('+ .uk-sticky-placeholder', this.$el) || $('<div class="uk-sticky-placeholder"></div>');
this.isFixed = false;
this.isActive = false;
},
disconnected() {
if (this.isFixed) {
this.hide();
removeClass(this.selTarget, this.clsInactive);
}
remove(this.placeholder);
this.placeholder = null;
this.widthElement = null;
},
events: [
{
name: 'load hashchange popstate',
el() {
return window;
},
handler() {
if (!(this.targetOffset !== false && location.hash && window.pageYOffset > 0)) {
return;
}
const target = $(location.hash);
if (target) {
fastdom.read(() => {
const {top} = offset(target);
const elTop = offset(this.$el).top;
const elHeight = this.$el.offsetHeight;
if (this.isFixed && elTop + elHeight >= top && elTop <= top + target.offsetHeight) {
scrollTop(window, top - elHeight - (isNumeric(this.targetOffset) ? this.targetOffset : 0) - this.offset);
}
});
}
}
}
],
update: [
{
read({height}, types) {
this.inactive = !this.matchMedia || !isVisible(this.$el);
if (this.inactive) {
return false;
}
if (this.isActive && types.has('resize')) {
this.hide();
height = this.$el.offsetHeight;
this.show();
}
height = this.isActive ? height : this.$el.offsetHeight;
if (height + this.offset > getHeight(window)) {
this.inactive = true;
return false;
}
const referenceElement = this.isFixed ? this.placeholder : this.$el;
this.topOffset = offset(referenceElement).top;
this.bottomOffset = this.topOffset + height;
this.offsetParentTop = offset(referenceElement.offsetParent).top;
const bottom = parseProp('bottom', this);
this.top = Math.max(toFloat(parseProp('top', this)), this.topOffset) - this.offset;
this.bottom = bottom && bottom - this.$el.offsetHeight;
this.width = dimensions(isVisible(this.widthElement) ? this.widthElement : this.$el).width;
return {
height,
top: offsetPosition(this.placeholder)[0],
margins: css(this.$el, ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'])
};
},
write({height, margins}) {
const {placeholder} = this;
css(placeholder, assign({height}, margins));
if (!within(placeholder, document)) {
after(this.$el, placeholder);
placeholder.hidden = true;
}
this.isActive = !!this.isActive; // force self-assign
},
events: ['resize']
},
{
read({scroll = 0}) {
this.scroll = window.pageYOffset;
return {
dir: scroll <= this.scroll ? 'down' : 'up',
scroll: this.scroll
};
},
write(data, types) {
const now = Date.now();
const isScrollUpdate = types.has('scroll');
const {initTimestamp = 0, dir, lastDir, lastScroll, scroll, top} = data;
data.lastScroll = scroll;
if (scroll < 0 || scroll === lastScroll && isScrollUpdate || this.showOnUp && !isScrollUpdate && !this.isFixed) {
return;
}
if (now - initTimestamp > 300 || dir !== lastDir) {
data.initScroll = scroll;
data.initTimestamp = now;
}
data.lastDir = dir;
if (this.showOnUp && !this.isFixed && Math.abs(data.initScroll - scroll) <= 30 && Math.abs(lastScroll - scroll) <= 10) {
return;
}
if (this.inactive
|| scroll < this.top
|| this.showOnUp && (scroll <= this.top || dir === 'down' && isScrollUpdate || dir === 'up' && !this.isFixed && scroll <= this.bottomOffset)
) {
if (!this.isFixed) {
if (Animation.inProgress(this.$el) && top > scroll) {
Animation.cancel(this.$el);
this.hide();
}
return;
}
this.isFixed = false;
if (this.animation && scroll > this.topOffset) {
Animation.cancel(this.$el);
Animation.out(this.$el, this.animation).then(() => this.hide(), noop);
} else {
this.hide();
}
} else if (this.isFixed) {
this.update();
} else if (this.animation) {
Animation.cancel(this.$el);
this.show();
Animation.in(this.$el, this.animation).catch(noop);
} else {
this.show();
}
},
events: ['resize', 'scroll']
}
],
methods: {
show() {
this.isFixed = true;
this.update();
this.placeholder.hidden = false;
},
hide() {
this.isActive = false;
removeClass(this.$el, this.clsFixed, this.clsBelow);
css(this.$el, {position: '', top: '', width: ''});
this.placeholder.hidden = true;
},
update() {
const active = this.top !== 0 || this.scroll > this.top;
let top = Math.max(0, this.offset);
let position = 'fixed';
if (isNumeric(this.bottom) && this.scroll > this.bottom - this.offset) {
top = this.bottom - this.offsetParentTop;
position = 'absolute';
}
css(this.$el, {
position,
top: `${top}px`,
width: this.width
});
this.isActive = active;
toggleClass(this.$el, this.clsBelow, this.scroll > this.bottomOffset);
addClass(this.$el, this.clsFixed);
}
}
};
function parseProp(prop, {$props, $el, [`${prop}Offset`]: propOffset}) {
const value = $props[prop];
if (!value) {
return;
}
if (isString(value) && value.match(/^-?\d/)) {
return propOffset + toPx(value);
} else {
return offset(value === true ? parent($el) : query(value, $el)).bottom;
}
}

View File

@@ -0,0 +1,237 @@
import {$, $$, after, ajax, append, attr, includes, isVisible, isVoidElement, memoize, noop, Promise, remove, removeAttr, startsWith, toFloat} from 'uikit-util';
export default {
args: 'src',
props: {
id: Boolean,
icon: String,
src: String,
style: String,
width: Number,
height: Number,
ratio: Number,
class: String,
strokeAnimation: Boolean,
focusable: Boolean, // IE 11
attributes: 'list'
},
data: {
ratio: 1,
include: ['style', 'class', 'focusable'],
class: '',
strokeAnimation: false
},
beforeConnect() {
this.class += ' uk-svg';
},
connected() {
if (!this.icon && includes(this.src, '#')) {
[this.src, this.icon] = this.src.split('#');
}
this.svg = this.getSvg().then(el => {
if (this._connected) {
const svg = insertSVG(el, this.$el);
if (this.svgEl && svg !== this.svgEl) {
remove(this.svgEl);
}
this.applyAttributes(svg, el);
this.$emit();
return this.svgEl = svg;
}
}, noop);
},
disconnected() {
this.svg.then(svg => {
if (!this._connected) {
if (isVoidElement(this.$el)) {
this.$el.hidden = false;
}
remove(svg);
this.svgEl = null;
}
});
this.svg = null;
},
update: {
read() {
return !!(this.strokeAnimation && this.svgEl && isVisible(this.svgEl));
},
write() {
applyAnimation(this.svgEl);
},
type: ['resize']
},
methods: {
getSvg() {
return loadSVG(this.src).then(svg =>
parseSVG(svg, this.icon) || Promise.reject('SVG not found.')
);
},
applyAttributes(el, ref) {
for (const prop in this.$options.props) {
if (includes(this.include, prop) && (prop in this)) {
attr(el, prop, this[prop]);
}
}
for (const attribute in this.attributes) {
const [prop, value] = this.attributes[attribute].split(':', 2);
attr(el, prop, value);
}
if (!this.id) {
removeAttr(el, 'id');
}
const props = ['width', 'height'];
let dimensions = props.map(prop => this[prop]);
if (!dimensions.some(val => val)) {
dimensions = props.map(prop => attr(ref, prop));
}
const viewBox = attr(ref, 'viewBox');
if (viewBox && !dimensions.some(val => val)) {
dimensions = viewBox.split(' ').slice(2);
}
dimensions.forEach((val, i) =>
attr(el, props[i], toFloat(val) * this.ratio || null)
);
}
}
};
const loadSVG = memoize(src =>
new Promise((resolve, reject) => {
if (!src) {
reject();
return;
}
if (startsWith(src, 'data:')) {
resolve(decodeURIComponent(src.split(',')[1]));
} else {
ajax(src).then(
xhr => resolve(xhr.response),
() => reject('SVG not found.')
);
}
})
);
function parseSVG(svg, icon) {
if (icon && includes(svg, '<symbol')) {
svg = parseSymbols(svg, icon) || svg;
}
svg = $(svg.substr(svg.indexOf('<svg')));
return svg && svg.hasChildNodes() && svg;
}
const symbolRe = /<symbol([^]*?id=(['"])(.+?)\2[^]*?<\/)symbol>/g;
const symbols = {};
function parseSymbols(svg, icon) {
if (!symbols[svg]) {
symbols[svg] = {};
symbolRe.lastIndex = 0;
let match;
while ((match = symbolRe.exec(svg))) {
symbols[svg][match[3]] = `<svg xmlns="http://www.w3.org/2000/svg"${match[1]}svg>`;
}
}
return symbols[svg][icon];
}
function applyAnimation(el) {
const length = getMaxPathLength(el);
if (length) {
el.style.setProperty('--uk-animation-stroke', length);
}
}
export function getMaxPathLength(el) {
return Math.ceil(Math.max(0, ...$$('[stroke]', el).map(stroke => {
try {
return stroke.getTotalLength();
} catch (e) {
return 0;
}
})));
}
function insertSVG(el, root) {
if (isVoidElement(root) || root.tagName === 'CANVAS') {
root.hidden = true;
const next = root.nextElementSibling;
return equals(el, next)
? next
: after(root, el);
}
const last = root.lastElementChild;
return equals(el, last)
? last
: append(root, el);
}
function equals(el, other) {
return isSVG(el) && isSVG(other) && innerHTML(el) === innerHTML(other);
}
function isSVG(el) {
return el && el.tagName === 'svg';
}
function innerHTML(el) {
return (el.innerHTML || (new XMLSerializer()).serializeToString(el).replace(/<svg.*?>(.*?)<\/svg>/g, '$1')).replace(/\s/g, '');
}

View File

@@ -0,0 +1,162 @@
import Togglable from '../mixin/togglable';
import {$$, attr, children, css, data, endsWith, findIndex, getIndex, hasClass, matches, queryAll, toggleClass, toNodes, within} from 'uikit-util';
export default {
mixins: [Togglable],
args: 'connect',
props: {
connect: String,
toggle: String,
itemNav: String,
active: Number,
swiping: Boolean
},
data: {
connect: '~.uk-switcher',
toggle: '> * > :first-child',
itemNav: false,
active: 0,
swiping: true,
cls: 'uk-active',
attrItem: 'uk-switcher-item'
},
computed: {
connects: {
get({connect}, $el) {
return queryAll(connect, $el);
},
watch(connects) {
if (this.swiping) {
css(connects, 'touch-action', 'pan-y pinch-zoom');
}
const index = this.index();
this.connects.forEach(el =>
children(el).forEach((child, i) =>
toggleClass(child, this.cls, i === index)
)
);
},
immediate: true
},
toggles: {
get({toggle}, $el) {
return $$(toggle, $el).filter(el => !matches(el, '.uk-disabled *, .uk-disabled, [disabled]'));
},
watch(toggles) {
const active = this.index();
this.show(~active ? active : toggles[this.active] || toggles[0]);
},
immediate: true
},
children() {
return children(this.$el).filter(child => this.toggles.some(toggle => within(toggle, child)));
}
},
events: [
{
name: 'click',
delegate() {
return this.toggle;
},
handler(e) {
e.preventDefault();
this.show(e.current);
}
},
{
name: 'click',
el() {
return this.connects.concat(this.itemNav ? queryAll(this.itemNav, this.$el) : []);
},
delegate() {
return `[${this.attrItem}],[data-${this.attrItem}]`;
},
handler(e) {
e.preventDefault();
this.show(data(e.current, this.attrItem));
}
},
{
name: 'swipeRight swipeLeft',
filter() {
return this.swiping;
},
el() {
return this.connects;
},
handler({type}) {
this.show(endsWith(type, 'Left') ? 'next' : 'previous');
}
}
],
methods: {
index() {
return findIndex(this.children, el => hasClass(el, this.cls));
},
show(item) {
const prev = this.index();
const next = getIndex(
this.children[getIndex(item, this.toggles, prev)],
children(this.$el)
);
if (prev === next) {
return;
}
this.children.forEach((child, i) => {
toggleClass(child, this.cls, next === i);
attr(this.toggles[i], 'aria-expanded', next === i);
});
this.connects.forEach(({children}) =>
this.toggleElement(toNodes(children).filter(child =>
hasClass(child, this.cls)
), false, prev >= 0).then(() =>
this.toggleElement(children[next], true, prev >= 0)
)
);
}
}
};

View File

@@ -0,0 +1,33 @@
import Switcher from './switcher';
import Class from '../mixin/class';
import {hasClass} from 'uikit-util';
export default {
mixins: [Class],
extends: Switcher,
props: {
media: Boolean
},
data: {
media: 960,
attrItem: 'uk-tab-item'
},
connected() {
const cls = hasClass(this.$el, 'uk-tab-left')
? 'uk-tab-left'
: hasClass(this.$el, 'uk-tab-right')
? 'uk-tab-right'
: false;
if (cls) {
this.$create('toggle', this.$el, {cls, mode: 'media', media: this.media});
}
}
};

View File

@@ -0,0 +1,248 @@
import Media from '../mixin/media';
import Togglable from '../mixin/togglable';
import {attr, closest, hasClass, includes, isBoolean, isFocusable, isTouch, matches, once, pointerDown, pointerEnter, pointerLeave, queryAll, trigger, within} from 'uikit-util';
const KEY_SPACE = 32;
export default {
mixins: [Media, Togglable],
args: 'target',
props: {
href: String,
target: null,
mode: 'list',
queued: Boolean
},
data: {
href: false,
target: false,
mode: 'click',
queued: true
},
connected() {
if (!includes(this.mode, 'media') && !isFocusable(this.$el)) {
attr(this.$el, 'tabindex', '0');
}
},
computed: {
target: {
get({href, target}, $el) {
target = queryAll(target || href, $el);
return target.length && target || [$el];
},
watch() {
this.updateAria();
},
immediate: true
}
},
events: [
{
name: pointerDown,
filter() {
return includes(this.mode, 'hover');
},
handler(e) {
if (!isTouch(e) || this._showState) {
return;
}
// Clicking a button does not give it focus on all browsers and platforms
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
trigger(this.$el, 'focus');
once(document, pointerDown, () => trigger(this.$el, 'blur'), true, e => !within(e.target, this.$el));
// Prevent initial click to prevent double toggle through focus + click
if (includes(this.mode, 'click')) {
this._preventClick = true;
}
}
},
{
name: `${pointerEnter} ${pointerLeave} focus blur`,
filter() {
return includes(this.mode, 'hover');
},
handler(e) {
if (isTouch(e)) {
return;
}
const show = includes([pointerEnter, 'focus'], e.type);
const expanded = attr(this.$el, 'aria-expanded');
// Skip hide if still hovered or focused
if (!show && (
e.type === pointerLeave && matches(this.$el, ':focus')
|| e.type === 'blur' && matches(this.$el, ':hover')
)) {
return;
}
// Skip if state does not change e.g. hover + focus received
if (this._showState && show === (expanded !== this._showState)) {
// Ensure reset if state has changed through click
if (!show) {
this._showState = null;
}
return;
}
this._showState = show ? expanded : null;
this.toggle(`toggle${show ? 'show' : 'hide'}`);
}
},
{
name: 'keydown',
filter() {
return includes(this.mode, 'click') && this.$el.tagName !== 'INPUT';
},
handler(e) {
if (e.keyCode === KEY_SPACE) {
e.preventDefault();
this.$el.click();
}
}
},
{
name: 'click',
filter() {
return includes(this.mode, 'click');
},
handler(e) {
if (this._preventClick) {
return this._preventClick = null;
}
let link;
if (closest(e.target, 'a[href="#"], a[href=""]')
|| (link = closest(e.target, 'a[href]')) && (
attr(this.$el, 'aria-expanded') !== 'true'
|| link.hash && matches(this.target, link.hash)
)
) {
e.preventDefault();
}
this.toggle();
}
},
{
name: 'toggled',
self: true,
el() {
return this.target;
},
handler(e, toggled) {
if (e.target === this.target[0]) {
this.updateAria(toggled);
}
}
}
],
update: {
read() {
return includes(this.mode, 'media') && this.media
? {match: this.matchMedia}
: false;
},
write({match}) {
const toggled = this.isToggled(this.target);
if (match ? !toggled : toggled) {
this.toggle();
}
},
events: ['resize']
},
methods: {
toggle(type) {
if (!trigger(this.target, type || 'toggle', [this])) {
return;
}
if (!this.queued) {
return this.toggleElement(this.target);
}
const leaving = this.target.filter(el => hasClass(el, this.clsLeave));
if (leaving.length) {
this.target.forEach(el => {
const isLeaving = includes(leaving, el);
this.toggleElement(el, isLeaving, isLeaving);
});
return;
}
const toggled = this.target.filter(this.isToggled);
this.toggleElement(toggled, false).then(() =>
this.toggleElement(this.target.filter(el =>
!includes(toggled, el)
), true)
);
},
updateAria(toggled) {
if (includes(this.mode, 'media')) {
return;
}
attr(this.$el, 'aria-expanded', isBoolean(toggled)
? toggled
: this.isToggled(this.target)
);
}
}
};

View File

@@ -0,0 +1,65 @@
import {css, hasAttr, isInView, isVideo, isVisible, mute, pause, play} from 'uikit-util';
export default {
args: 'autoplay',
props: {
automute: Boolean,
autoplay: Boolean
},
data: {
automute: false,
autoplay: true
},
computed: {
inView({autoplay}) {
return autoplay === 'inview';
}
},
connected() {
if (this.inView && !hasAttr(this.$el, 'preload')) {
this.$el.preload = 'none';
}
if (this.automute) {
mute(this.$el);
}
},
update: {
read() {
if (!isVideo(this.$el)) {
return false;
}
return {
visible: isVisible(this.$el) && css(this.$el, 'visibility') !== 'hidden',
inView: this.inView && isInView(this.$el)
};
},
write({visible, inView}) {
if (!visible || this.inView && !inView) {
pause(this.$el);
} else if (this.autoplay === true || this.inView && inView) {
play(this.$el);
}
},
events: ['resize', 'scroll']
}
};

View File

@@ -0,0 +1,38 @@
import fade from './internal/animate-fade';
import slide from './internal/animate-slide';
import {noop, Promise} from 'uikit-util';
export default {
props: {
duration: Number,
animation: Boolean
},
data: {
duration: 150,
animation: 'slide'
},
methods: {
animate(action, target = this.$el) {
const name = this.animation;
const animationFn = name === 'fade'
? fade
: name === 'delayed-fade'
? (...args) => fade(...args, 40)
: name
? slide
: () => {
action();
return Promise.resolve();
};
return animationFn(action, target, this.duration)
.then(() => this.$update(target, 'resize'), noop);
}
}
};

View File

@@ -0,0 +1,9 @@
import {addClass, hasClass} from 'uikit-util';
export default {
connected() {
!hasClass(this.$el, this.$name) && addClass(this.$el, this.$name);
}
};

View File

@@ -0,0 +1,21 @@
import {$} from 'uikit-util';
export default {
props: {
container: Boolean
},
data: {
container: true
},
computed: {
container({container}) {
return container === true && this.$container || container && $(container);
}
}
};

View File

@@ -0,0 +1,56 @@
import {$$, boxModelAdjust, css, isIE, toFloat} from 'uikit-util';
// IE 11 fix (min-height on a flex container won't apply to its flex items)
export default isIE ? {
props: {
selMinHeight: String
},
data: {
selMinHeight: false,
forceHeight: false
},
computed: {
elements({selMinHeight}, $el) {
return selMinHeight ? $$(selMinHeight, $el) : [$el];
}
},
update: [
{
read() {
css(this.elements, 'height', '');
},
order: -5,
events: ['resize']
},
{
write() {
this.elements.forEach(el => {
const height = toFloat(css(el, 'minHeight'));
if (height && (this.forceHeight || Math.round(height + boxModelAdjust(el, 'height', 'content-box')) >= el.offsetHeight)) {
css(el, 'height', height);
}
});
},
order: 5,
events: ['resize']
}
]
} : {};

View File

@@ -0,0 +1,97 @@
import {getRows} from '../../core/margin';
import {addClass, children, css, hasClass, height, isInView, once, Promise, removeClass, sortBy, toNumber, Transition} from 'uikit-util';
const clsLeave = 'uk-transition-leave';
const clsEnter = 'uk-transition-enter';
export default function fade(action, target, duration, stagger = 0) {
const index = transitionIndex(target, true);
const propsIn = {opacity: 1};
const propsOut = {opacity: 0};
const wrapIndexFn = fn =>
() => index === transitionIndex(target) ? fn() : Promise.reject();
const leaveFn = wrapIndexFn(() => {
addClass(target, clsLeave);
return Promise.all(getTransitionNodes(target).map((child, i) =>
new Promise(resolve =>
setTimeout(() => Transition.start(child, propsOut, duration / 2, 'ease').then(resolve), i * stagger)
)
)).then(() => removeClass(target, clsLeave));
});
const enterFn = wrapIndexFn(() => {
const oldHeight = height(target);
addClass(target, clsEnter);
action();
css(children(target), {opacity: 0});
// Ensure UIkit updates have propagated
return new Promise(resolve =>
requestAnimationFrame(() => {
const nodes = children(target);
const newHeight = height(target);
// Ensure Grid cells do not stretch when height is applied
css(target, 'alignContent', 'flex-start');
height(target, oldHeight);
const transitionNodes = getTransitionNodes(target);
css(nodes, propsOut);
const transitions = transitionNodes.map((child, i) =>
new Promise(resolve =>
setTimeout(() => Transition.start(child, propsIn, duration / 2, 'ease').then(resolve), i * stagger)
)
);
if (oldHeight !== newHeight) {
transitions.push(Transition.start(target, {height: newHeight}, duration / 2 + transitionNodes.length * stagger, 'ease'));
}
Promise.all(transitions).then(() => {
removeClass(target, clsEnter);
if (index === transitionIndex(target)) {
css(target, {height: '', alignContent: ''});
css(nodes, {opacity: ''});
delete target.dataset.transition;
}
resolve();
});
})
);
});
return hasClass(target, clsLeave)
? waitTransitionend(target).then(enterFn)
: hasClass(target, clsEnter)
? waitTransitionend(target).then(leaveFn).then(enterFn)
: leaveFn().then(enterFn);
}
function transitionIndex(target, next) {
if (next) {
target.dataset.transition = 1 + transitionIndex(target);
}
return toNumber(target.dataset.transition) || 0;
}
function waitTransitionend(target) {
return Promise.all(children(target).filter(Transition.inProgress).map(el =>
new Promise(resolve => once(el, 'transitionend transitioncanceled', resolve))
));
}
function getTransitionNodes(target) {
return getRows(children(target)).reduce((nodes, row) => nodes.concat(sortBy(row.filter(el => isInView(el)), 'offsetLeft')), []);
}

View File

@@ -0,0 +1,131 @@
import {assign, children, css, fastdom, includes, index, isVisible, noop, offset, parent, position, Promise, Transition} from 'uikit-util';
export default function (action, target, duration) {
return new Promise(resolve =>
requestAnimationFrame(() => {
let nodes = children(target);
// Get current state
const currentProps = nodes.map(el => getProps(el, true));
const targetProps = css(target, ['height', 'padding']);
// Cancel previous animations
Transition.cancel(target);
nodes.forEach(Transition.cancel);
reset(target);
// Adding, sorting, removing nodes
action();
// Find new nodes
nodes = nodes.concat(children(target).filter(el => !includes(nodes, el)));
// Wait for update to propagate
Promise.resolve().then(() => {
// Force update
fastdom.flush();
// Get new state
const targetPropsTo = css(target, ['height', 'padding']);
const [propsTo, propsFrom] = getTransitionProps(target, nodes, currentProps);
// Reset to previous state
nodes.forEach((el, i) => propsFrom[i] && css(el, propsFrom[i]));
css(target, assign({display: 'block'}, targetProps));
// Start transitions on next frame
requestAnimationFrame(() => {
const transitions = nodes.map((el, i) =>
parent(el) === target && Transition.start(el, propsTo[i], duration, 'ease')
).concat(Transition.start(target, targetPropsTo, duration, 'ease'));
Promise.all(transitions).then(() => {
nodes.forEach((el, i) => parent(el) === target && css(el, 'display', propsTo[i].opacity === 0 ? 'none' : ''));
reset(target);
}, noop).then(resolve);
});
});
}));
}
function getProps(el, opacity) {
const zIndex = css(el, 'zIndex');
return isVisible(el)
? assign({
display: '',
opacity: opacity ? css(el, 'opacity') : '0',
pointerEvents: 'none',
position: 'absolute',
zIndex: zIndex === 'auto' ? index(el) : zIndex
}, getPositionWithMargin(el))
: false;
}
function getTransitionProps(target, nodes, currentProps) {
const propsTo = nodes.map((el, i) =>
parent(el) && i in currentProps
? currentProps[i]
? isVisible(el)
? getPositionWithMargin(el)
: {opacity: 0}
: {opacity: isVisible(el) ? 1 : 0}
: false);
const propsFrom = propsTo.map((props, i) => {
const from = parent(nodes[i]) === target && (currentProps[i] || getProps(nodes[i]));
if (!from) {
return false;
}
if (!props) {
delete from.opacity;
} else if (!('opacity' in props)) {
const {opacity} = from;
if (opacity % 1) {
props.opacity = 1;
} else {
delete from.opacity;
}
}
return from;
});
return [propsTo, propsFrom];
}
function reset(el) {
css(el.children, {
height: '',
left: '',
opacity: '',
pointerEvents: '',
position: '',
top: '',
marginTop: '',
marginLeft: '',
transform: '',
width: '',
zIndex: ''
});
css(el, {height: '', display: '', padding: ''});
}
function getPositionWithMargin(el) {
const {height, width} = offset(el);
const {top, left} = position(el);
const {marginLeft, marginTop} = css(el, ['marginTop', 'marginLeft']);
return {top, left, height, width, marginLeft, marginTop, transform: ''};
}

View File

@@ -0,0 +1,40 @@
import {css, isIE} from 'uikit-util';
export default {
slide: {
show(dir) {
return [
{transform: translate(dir * -100)},
{transform: translate()}
];
},
percent(current) {
return translated(current);
},
translate(percent, dir) {
return [
{transform: translate(dir * -100 * percent)},
{transform: translate(dir * 100 * (1 - percent))}
];
}
}
};
export function translated(el) {
return Math.abs(css(el, 'transform').split(',')[4] / el.offsetWidth) || 0;
}
export function translate(value = 0, unit = '%') {
value += value ? unit : '';
return isIE ? `translateX(${value})` : `translate3d(${value}, 0, 0)`; // currently, not translate3d in IE, translate3d within translate3d does not work while transitioning
}
export function scale3d(value) {
return `scale3d(${value}, ${value}, 1)`;
}

View File

@@ -0,0 +1,75 @@
import {clamp, createEvent, css, Deferred, noop, Promise, Transition, trigger} from 'uikit-util';
export default function Transitioner(prev, next, dir, {animation, easing}) {
const {percent, translate, show = noop} = animation;
const props = show(dir);
const deferred = new Deferred();
return {
dir,
show(duration, percent = 0, linear) {
const timing = linear ? 'linear' : easing;
duration -= Math.round(duration * clamp(percent, -1, 1));
this.translate(percent);
triggerUpdate(next, 'itemin', {percent, duration, timing, dir});
triggerUpdate(prev, 'itemout', {percent: 1 - percent, duration, timing, dir});
Promise.all([
Transition.start(next, props[1], duration, timing),
Transition.start(prev, props[0], duration, timing)
]).then(() => {
this.reset();
deferred.resolve();
}, noop);
return deferred.promise;
},
cancel() {
Transition.cancel([next, prev]);
},
reset() {
for (const prop in props[0]) {
css([next, prev], prop, '');
}
},
forward(duration, percent = this.percent()) {
Transition.cancel([next, prev]);
return this.show(duration, percent, true);
},
translate(percent) {
this.reset();
const props = translate(percent, dir);
css(next, props[1]);
css(prev, props[0]);
triggerUpdate(next, 'itemtranslatein', {percent, dir});
triggerUpdate(prev, 'itemtranslateout', {percent: 1 - percent, dir});
},
percent() {
return percent(prev || next, next, dir);
},
getDistance() {
return prev && prev.offsetWidth;
}
};
}
function triggerUpdate(el, type, data) {
trigger(el, createEvent(type, false, false, data));
}

View File

@@ -0,0 +1,36 @@
import {getCssVar, isString, toFloat} from 'uikit-util';
export default {
props: {
media: Boolean
},
data: {
media: false
},
computed: {
matchMedia() {
const media = toMedia(this.media);
return !media || window.matchMedia(media).matches;
}
}
};
function toMedia(value) {
if (isString(value)) {
if (value[0] === '@') {
const name = `breakpoint-${value.substr(1)}`;
value = toFloat(getCssVar(name));
} else if (isNaN(value)) {
return value;
}
}
return value && !isNaN(value) ? `(min-width: ${value}px)` : false;
}

View File

@@ -0,0 +1,248 @@
import {$, addClass, append, attr, css, includes, isFocusable, last, on, once, parent, pointerCancel, pointerDown, pointerUp, Promise, removeClass, toFloat, toMs, width, within} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';
const active = [];
export default {
mixins: [Class, Container, Togglable],
props: {
selPanel: String,
selClose: String,
escClose: Boolean,
bgClose: Boolean,
stack: Boolean
},
data: {
cls: 'uk-open',
escClose: true,
bgClose: true,
overlay: true,
stack: false
},
computed: {
panel({selPanel}, $el) {
return $(selPanel, $el);
},
transitionElement() {
return this.panel;
},
bgClose({bgClose}) {
return bgClose && this.panel;
}
},
beforeDisconnect() {
if (includes(active, this)) {
this.toggleElement(this.$el, false, false);
}
},
events: [
{
name: 'click',
delegate() {
return this.selClose;
},
handler(e) {
e.preventDefault();
this.hide();
}
},
{
name: 'toggle',
self: true,
handler(e, toggle) {
if (e.defaultPrevented) {
return;
}
e.preventDefault();
if (this.isToggled() === includes(active, this)) {
this.toggle();
}
}
},
{
name: 'beforeshow',
self: true,
handler(e) {
if (includes(active, this)) {
return false;
}
if (!this.stack && active.length) {
Promise.all(active.map(modal => modal.hide())).then(this.show);
e.preventDefault();
} else {
active.push(this);
}
}
},
{
name: 'show',
self: true,
handler() {
const docEl = document.documentElement;
if (width(window) > docEl.clientWidth && this.overlay) {
css(document.body, 'overflowY', 'scroll');
}
if (this.stack) {
css(this.$el, 'zIndex', toFloat(css(this.$el, 'zIndex')) + active.length);
}
addClass(docEl, this.clsPage);
if (this.bgClose) {
once(this.$el, 'hide', on(document, pointerDown, ({target}) => {
if (last(active) !== this || this.overlay && !within(target, this.$el) || within(target, this.panel)) {
return;
}
once(document, `${pointerUp} ${pointerCancel} scroll`, ({defaultPrevented, type, target: newTarget}) => {
if (!defaultPrevented && type === pointerUp && target === newTarget) {
this.hide();
}
}, true);
}), {self: true});
}
if (this.escClose) {
once(this.$el, 'hide', on(document, 'keydown', e => {
if (e.keyCode === 27 && last(active) === this) {
this.hide();
}
}), {self: true});
}
}
},
{
name: 'shown',
self: true,
handler() {
if (!isFocusable(this.$el)) {
attr(this.$el, 'tabindex', '-1');
}
if (!$(':focus', this.$el)) {
this.$el.focus();
}
}
},
{
name: 'hidden',
self: true,
handler() {
if (includes(active, this)) {
active.splice(active.indexOf(this), 1);
}
if (!active.length) {
css(document.body, 'overflowY', '');
}
css(this.$el, 'zIndex', '');
if (!active.some(modal => modal.clsPage === this.clsPage)) {
removeClass(document.documentElement, this.clsPage);
}
}
}
],
methods: {
toggle() {
return this.isToggled() ? this.hide() : this.show();
},
show() {
if (this.container && parent(this.$el) !== this.container) {
append(this.container, this.$el);
return new Promise(resolve =>
requestAnimationFrame(() =>
this.show().then(resolve)
)
);
}
return this.toggleElement(this.$el, true, animate(this));
},
hide() {
return this.toggleElement(this.$el, false, animate(this));
}
}
};
function animate({transitionElement, _toggle}) {
return (el, show) =>
new Promise((resolve, reject) =>
once(el, 'show hide', () => {
el._reject && el._reject();
el._reject = reject;
_toggle(el, show);
const off = once(transitionElement, 'transitionstart', () => {
once(transitionElement, 'transitionend transitioncancel', resolve, {self: true});
clearTimeout(timer);
}, {self: true});
const timer = setTimeout(() => {
off();
resolve();
}, toMs(css(transitionElement, 'transitionDuration')));
})
).then(() => delete el._reject);
}

View File

@@ -0,0 +1,333 @@
import Media from '../mixin/media';
import {getMaxPathLength} from '../core/svg';
import {css, Dimensions, each, isNumber, isString, isUndefined, startsWith, toFloat, toPx, ucfirst} from 'uikit-util';
const props = ['x', 'y', 'bgx', 'bgy', 'rotate', 'scale', 'color', 'backgroundColor', 'borderColor', 'opacity', 'blur', 'hue', 'grayscale', 'invert', 'saturate', 'sepia', 'fopacity', 'stroke'];
export default {
mixins: [Media],
props: props.reduce((props, prop) => {
props[prop] = 'list';
return props;
}, {}),
data: props.reduce((data, prop) => {
data[prop] = undefined;
return data;
}, {}),
computed: {
props(properties, $el) {
return props.reduce((props, prop) => {
if (isUndefined(properties[prop])) {
return props;
}
const isColor = prop.match(/color/i);
const isCssProp = isColor || prop === 'opacity';
let pos, bgPos, diff;
let steps = properties[prop].slice();
if (isCssProp) {
css($el, prop, '');
}
if (steps.length < 2) {
steps.unshift((prop === 'scale'
? 1
: isCssProp
? css($el, prop)
: 0) || 0);
}
const unit = getUnit(steps, prop);
if (isColor) {
const {color} = $el.style;
steps = steps.map(step => parseColor($el, step));
$el.style.color = color;
} else if (startsWith(prop, 'bg')) {
const attr = prop === 'bgy' ? 'height' : 'width';
steps = steps.map(step => toPx(step, attr, $el));
css($el, `background-position-${prop[2]}`, '');
bgPos = css($el, 'backgroundPosition').split(' ')[prop[2] === 'x' ? 0 : 1]; // IE 11 can't read background-position-[x|y]
if (this.covers) {
const min = Math.min(...steps);
const max = Math.max(...steps);
const down = steps.indexOf(min) < steps.indexOf(max);
diff = max - min;
steps = steps.map(step => step - (down ? min : max));
pos = `${down ? -diff : 0}px`;
} else {
pos = bgPos;
}
} else {
steps = steps.map(toFloat);
}
if (prop === 'stroke') {
if (!steps.some(step => step)) {
return props;
}
const length = getMaxPathLength($el);
css($el, 'strokeDasharray', length);
if (unit === '%') {
steps = steps.map(step => step * length / 100);
}
steps = steps.reverse();
prop = 'strokeDashoffset';
}
props[prop] = {steps, unit, pos, bgPos, diff};
return props;
}, {});
},
bgProps() {
return ['bgx', 'bgy'].filter(bg => bg in this.props);
},
covers(_, $el) {
return covers($el);
}
},
disconnected() {
delete this._image;
},
update: {
read(data) {
if (!this.matchMedia) {
return;
}
if (!data.image && this.covers && this.bgProps.length) {
const src = css(this.$el, 'backgroundImage').replace(/^none|url\(["']?(.+?)["']?\)$/, '$1');
if (src) {
const img = new Image();
img.src = src;
data.image = img;
if (!img.naturalWidth) {
img.onload = () => this.$update();
}
}
}
const {image} = data;
if (!image || !image.naturalWidth) {
return;
}
const dimEl = {
width: this.$el.offsetWidth,
height: this.$el.offsetHeight
};
const dimImage = {
width: image.naturalWidth,
height: image.naturalHeight
};
let dim = Dimensions.cover(dimImage, dimEl);
this.bgProps.forEach(prop => {
const {diff, bgPos, steps} = this.props[prop];
const attr = prop === 'bgy' ? 'height' : 'width';
const span = dim[attr] - dimEl[attr];
if (span < diff) {
dimEl[attr] = dim[attr] + diff - span;
} else if (span > diff) {
const posPercentage = dimEl[attr] / toPx(bgPos, attr, this.$el);
if (posPercentage) {
this.props[prop].steps = steps.map(step => step - (span - diff) / posPercentage);
}
}
dim = Dimensions.cover(dimImage, dimEl);
});
data.dim = dim;
},
write({dim}) {
if (!this.matchMedia) {
css(this.$el, {backgroundSize: '', backgroundRepeat: ''});
return;
}
dim && css(this.$el, {
backgroundSize: `${dim.width}px ${dim.height}px`,
backgroundRepeat: 'no-repeat'
});
},
events: ['resize']
},
methods: {
reset() {
each(this.getCss(0), (_, prop) => css(this.$el, prop, ''));
},
getCss(percent) {
const {props} = this;
return Object.keys(props).reduce((css, prop) => {
let {steps, unit, pos} = props[prop];
const value = getValue(steps, percent);
switch (prop) {
// transforms
case 'x':
case 'y': {
unit = unit || 'px';
css.transform += ` translate${ucfirst(prop)}(${
toFloat(value).toFixed(unit === 'px' ? 0 : 2)
}${unit})`;
break;
}
case 'rotate':
unit = unit || 'deg';
css.transform += ` rotate(${value + unit})`;
break;
case 'scale':
css.transform += ` scale(${value})`;
break;
// bg image
case 'bgy':
case 'bgx':
css[`background-position-${prop[2]}`] = `calc(${pos} + ${value}px)`;
break;
// color
case 'color':
case 'backgroundColor':
case 'borderColor': {
const [start, end, p] = getStep(steps, percent);
css[prop] = `rgba(${
start.map((value, i) => {
value += p * (end[i] - value);
return i === 3 ? toFloat(value) : parseInt(value, 10);
}).join(',')
})`;
break;
}
// CSS Filter
case 'blur':
unit = unit || 'px';
css.filter += ` blur(${value + unit})`;
break;
case 'hue':
unit = unit || 'deg';
css.filter += ` hue-rotate(${value + unit})`;
break;
case 'fopacity':
unit = unit || '%';
css.filter += ` opacity(${value + unit})`;
break;
case 'grayscale':
case 'invert':
case 'saturate':
case 'sepia':
unit = unit || '%';
css.filter += ` ${prop}(${value + unit})`;
break;
default:
css[prop] = value;
}
return css;
}, {transform: '', filter: ''});
}
}
};
function parseColor(el, color) {
return css(css(el, 'color', color), 'color')
.split(/[(),]/g)
.slice(1, -1)
.concat(1)
.slice(0, 4)
.map(toFloat);
}
function getStep(steps, percent) {
const count = steps.length - 1;
const index = Math.min(Math.floor(count * percent), count - 1);
const step = steps.slice(index, index + 2);
step.push(percent === 1 ? 1 : percent % (1 / count) * count);
return step;
}
function getValue(steps, percent, digits = 2) {
const [start, end, p] = getStep(steps, percent);
return (isNumber(start)
? start + Math.abs(start - end) * p * (start < end ? 1 : -1)
: +end
).toFixed(digits);
}
function getUnit(steps) {
return steps.reduce((unit, step) => isString(step) && step.replace(/-|\d/g, '').trim() || unit, '');
}
function covers(el) {
const {backgroundSize} = el.style;
const covers = css(css(el, 'backgroundSize', ''), 'backgroundSize') === 'cover';
el.style.backgroundSize = backgroundSize;
return covers;
}

View File

@@ -0,0 +1,75 @@
import {$, flipPosition, offset as getOffset, isNumeric, isRtl, positionAt, removeClasses, toggleClass} from 'uikit-util';
export default {
props: {
pos: String,
offset: null,
flip: Boolean,
clsPos: String
},
data: {
pos: `bottom-${isRtl ? 'right' : 'left'}`,
flip: true,
offset: false,
clsPos: ''
},
computed: {
pos({pos}) {
return pos.split('-').concat('center').slice(0, 2);
},
dir() {
return this.pos[0];
},
align() {
return this.pos[1];
}
},
methods: {
positionAt(element, target, boundary) {
removeClasses(element, `${this.clsPos}-(top|bottom|left|right)(-[a-z]+)?`);
let {offset} = this;
const axis = this.getAxis();
if (!isNumeric(offset)) {
const node = $(offset);
offset = node
? getOffset(node)[axis === 'x' ? 'left' : 'top'] - getOffset(target)[axis === 'x' ? 'right' : 'bottom']
: 0;
}
const {x, y} = positionAt(
element,
target,
axis === 'x' ? `${flipPosition(this.dir)} ${this.align}` : `${this.align} ${flipPosition(this.dir)}`,
axis === 'x' ? `${this.dir} ${this.align}` : `${this.align} ${this.dir}`,
axis === 'x' ? `${this.dir === 'left' ? -offset : offset}` : ` ${this.dir === 'top' ? -offset : offset}`,
null,
this.flip,
boundary
).target;
this.dir = axis === 'x' ? x : y;
this.align = axis === 'x' ? y : x;
toggleClass(element, `${this.clsPos}-${this.dir}-${this.align}`, this.offset === false);
},
getAxis() {
return this.dir === 'top' || this.dir === 'bottom' ? 'y' : 'x';
}
}
};

View File

@@ -0,0 +1,77 @@
import {$, attr, matches} from 'uikit-util';
export default {
props: {
autoplay: Boolean,
autoplayInterval: Number,
pauseOnHover: Boolean
},
data: {
autoplay: false,
autoplayInterval: 7000,
pauseOnHover: true
},
connected() {
this.autoplay && this.startAutoplay();
},
disconnected() {
this.stopAutoplay();
},
update() {
attr(this.slides, 'tabindex', '-1');
},
events: [
{
name: 'visibilitychange',
el() {
return document;
},
filter() {
return this.autoplay;
},
handler() {
if (document.hidden) {
this.stopAutoplay();
} else {
this.startAutoplay();
}
}
}
],
methods: {
startAutoplay() {
this.stopAutoplay();
this.interval = setInterval(
() => (!this.draggable || !$(':focus', this.$el))
&& (!this.pauseOnHover || !matches(this.$el, ':hover'))
&& !this.stack.length
&& this.show('next'),
this.autoplayInterval
);
},
stopAutoplay() {
this.interval && clearInterval(this.interval);
}
}
};

View File

@@ -0,0 +1,215 @@
import {closest, css, getEventPos, includes, isRtl, isTouch, off, on, pointerCancel, pointerDown, pointerMove, pointerUp, selInput, trigger} from 'uikit-util';
export default {
props: {
draggable: Boolean
},
data: {
draggable: true,
threshold: 10
},
created() {
['start', 'move', 'end'].forEach(key => {
const fn = this[key];
this[key] = e => {
const pos = getEventPos(e).x * (isRtl ? -1 : 1);
this.prevPos = pos !== this.pos ? this.pos : this.prevPos;
this.pos = pos;
fn(e);
};
});
},
events: [
{
name: pointerDown,
delegate() {
return this.selSlides;
},
handler(e) {
if (!this.draggable
|| !isTouch(e) && hasTextNodesOnly(e.target)
|| closest(e.target, selInput)
|| e.button > 0
|| this.length < 2
) {
return;
}
this.start(e);
}
},
{
name: 'dragstart',
handler(e) {
e.preventDefault();
}
}
],
methods: {
start() {
this.drag = this.pos;
if (this._transitioner) {
this.percent = this._transitioner.percent();
this.drag += this._transitioner.getDistance() * this.percent * this.dir;
this._transitioner.cancel();
this._transitioner.translate(this.percent);
this.dragging = true;
this.stack = [];
} else {
this.prevIndex = this.index;
}
on(document, pointerMove, this.move, {passive: false});
// 'input' event is triggered by video controls
on(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
css(this.list, 'userSelect', 'none');
},
move(e) {
const distance = this.pos - this.drag;
if (distance === 0 || this.prevPos === this.pos || !this.dragging && Math.abs(distance) < this.threshold) {
return;
}
// prevent click event
css(this.list, 'pointerEvents', 'none');
e.cancelable && e.preventDefault();
this.dragging = true;
this.dir = (distance < 0 ? 1 : -1);
const {slides} = this;
let {prevIndex} = this;
let dis = Math.abs(distance);
let nextIndex = this.getIndex(prevIndex + this.dir, prevIndex);
let width = this._getDistance(prevIndex, nextIndex) || slides[prevIndex].offsetWidth;
while (nextIndex !== prevIndex && dis > width) {
this.drag -= width * this.dir;
prevIndex = nextIndex;
dis -= width;
nextIndex = this.getIndex(prevIndex + this.dir, prevIndex);
width = this._getDistance(prevIndex, nextIndex) || slides[prevIndex].offsetWidth;
}
this.percent = dis / width;
const prev = slides[prevIndex];
const next = slides[nextIndex];
const changed = this.index !== nextIndex;
const edge = prevIndex === nextIndex;
let itemShown;
[this.index, this.prevIndex].filter(i => !includes([nextIndex, prevIndex], i)).forEach(i => {
trigger(slides[i], 'itemhidden', [this]);
if (edge) {
itemShown = true;
this.prevIndex = prevIndex;
}
});
if (this.index === prevIndex && this.prevIndex !== prevIndex || itemShown) {
trigger(slides[this.index], 'itemshown', [this]);
}
if (changed) {
this.prevIndex = prevIndex;
this.index = nextIndex;
!edge && trigger(prev, 'beforeitemhide', [this]);
trigger(next, 'beforeitemshow', [this]);
}
this._transitioner = this._translate(Math.abs(this.percent), prev, !edge && next);
if (changed) {
!edge && trigger(prev, 'itemhide', [this]);
trigger(next, 'itemshow', [this]);
}
},
end() {
off(document, pointerMove, this.move, {passive: false});
off(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
if (this.dragging) {
this.dragging = null;
if (this.index === this.prevIndex) {
this.percent = 1 - this.percent;
this.dir *= -1;
this._show(false, this.index, true);
this._transitioner = null;
} else {
const dirChange = (isRtl ? this.dir * (isRtl ? 1 : -1) : this.dir) < 0 === this.prevPos > this.pos;
this.index = dirChange ? this.index : this.prevIndex;
if (dirChange) {
this.percent = 1 - this.percent;
}
this.show(this.dir > 0 && !dirChange || this.dir < 0 && dirChange ? 'next' : 'previous', true);
}
}
css(this.list, {userSelect: '', pointerEvents: ''});
this.drag
= this.percent
= null;
}
}
};
function hasTextNodesOnly(el) {
return !el.children.length && el.childNodes.length;
}

View File

@@ -0,0 +1,86 @@
import {$, $$, data, html, toggleClass, toNumber} from 'uikit-util';
export default {
data: {
selNav: false
},
computed: {
nav({selNav}, $el) {
return $(selNav, $el);
},
selNavItem({attrItem}) {
return `[${attrItem}],[data-${attrItem}]`;
},
navItems(_, $el) {
return $$(this.selNavItem, $el);
}
},
update: {
write() {
if (this.nav && this.length !== this.nav.children.length) {
html(this.nav, this.slides.map((_, i) => `<li ${this.attrItem}="${i}"><a href></a></li>`).join(''));
}
this.navItems.concat(this.nav).forEach(el => el && (el.hidden = !this.maxIndex));
this.updateNav();
},
events: ['resize']
},
events: [
{
name: 'click',
delegate() {
return this.selNavItem;
},
handler(e) {
e.preventDefault();
this.show(data(e.current, this.attrItem));
}
},
{
name: 'itemshow',
handler: 'updateNav'
}
],
methods: {
updateNav() {
const i = this.getValidIndex();
this.navItems.forEach(el => {
const cmd = data(el, this.attrItem);
toggleClass(el, this.clsActive, toNumber(cmd) === i);
toggleClass(el, 'uk-invisible', this.finite && (cmd === 'previous' && i === 0 || cmd === 'next' && i >= this.maxIndex));
});
}
}
};

View File

@@ -0,0 +1,23 @@
export default {
update: {
write() {
if (this.stack.length || this.dragging) {
return;
}
const index = this.getValidIndex(this.index);
if (!~this.prevIndex || this.index !== index) {
this.show(index);
}
},
events: ['resize']
}
};

View File

@@ -0,0 +1,232 @@
import SliderAutoplay from './slider-autoplay';
import SliderDrag from './slider-drag';
import SliderNav from './slider-nav';
import {$, $$, assign, clamp, fastdom, getIndex, hasClass, isNumber, isRtl, Promise, removeClass, trigger} from 'uikit-util';
export default {
mixins: [SliderAutoplay, SliderDrag, SliderNav],
props: {
clsActivated: Boolean,
easing: String,
index: Number,
finite: Boolean,
velocity: Number,
selSlides: String
},
data: () => ({
easing: 'ease',
finite: false,
velocity: 1,
index: 0,
prevIndex: -1,
stack: [],
percent: 0,
clsActive: 'uk-active',
clsActivated: false,
Transitioner: false,
transitionOptions: {}
}),
connected() {
this.prevIndex = -1;
this.index = this.getValidIndex(this.$props.index);
this.stack = [];
},
disconnected() {
removeClass(this.slides, this.clsActive);
},
computed: {
duration({velocity}, $el) {
return speedUp($el.offsetWidth / velocity);
},
list({selList}, $el) {
return $(selList, $el);
},
maxIndex() {
return this.length - 1;
},
selSlides({selList, selSlides}) {
return `${selList} ${selSlides || '> *'}`;
},
slides: {
get() {
return $$(this.selSlides, this.$el);
},
watch() {
this.$reset();
}
},
length() {
return this.slides.length;
}
},
events: {
itemshown() {
this.$update(this.list);
}
},
methods: {
show(index, force = false) {
if (this.dragging || !this.length) {
return;
}
const {stack} = this;
const queueIndex = force ? 0 : stack.length;
const reset = () => {
stack.splice(queueIndex, 1);
if (stack.length) {
this.show(stack.shift(), true);
}
};
stack[force ? 'unshift' : 'push'](index);
if (!force && stack.length > 1) {
if (stack.length === 2) {
this._transitioner.forward(Math.min(this.duration, 200));
}
return;
}
const prevIndex = this.getIndex(this.index);
const prev = hasClass(this.slides, this.clsActive) && this.slides[prevIndex];
const nextIndex = this.getIndex(index, this.index);
const next = this.slides[nextIndex];
if (prev === next) {
reset();
return;
}
this.dir = getDirection(index, prevIndex);
this.prevIndex = prevIndex;
this.index = nextIndex;
if (prev && !trigger(prev, 'beforeitemhide', [this])
|| !trigger(next, 'beforeitemshow', [this, prev])
) {
this.index = this.prevIndex;
reset();
return;
}
const promise = this._show(prev, next, force).then(() => {
prev && trigger(prev, 'itemhidden', [this]);
trigger(next, 'itemshown', [this]);
return new Promise(resolve => {
fastdom.write(() => {
stack.shift();
if (stack.length) {
this.show(stack.shift(), true);
} else {
this._transitioner = null;
}
resolve();
});
});
});
prev && trigger(prev, 'itemhide', [this]);
trigger(next, 'itemshow', [this]);
return promise;
},
getIndex(index = this.index, prev = this.index) {
return clamp(getIndex(index, this.slides, prev, this.finite), 0, this.maxIndex);
},
getValidIndex(index = this.index, prevIndex = this.prevIndex) {
return this.getIndex(index, prevIndex);
},
_show(prev, next, force) {
this._transitioner = this._getTransitioner(
prev,
next,
this.dir,
assign({
easing: force
? next.offsetWidth < 600
? 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' /* easeOutQuad */
: 'cubic-bezier(0.165, 0.84, 0.44, 1)' /* easeOutQuart */
: this.easing
}, this.transitionOptions)
);
if (!force && !prev) {
this._translate(1);
return Promise.resolve();
}
const {length} = this.stack;
return this._transitioner[length > 1 ? 'forward' : 'show'](length > 1 ? Math.min(this.duration, 75 + 75 / (length - 1)) : this.duration, this.percent);
},
_getDistance(prev, next) {
return this._getTransitioner(prev, prev !== next && next).getDistance();
},
_translate(percent, prev = this.prevIndex, next = this.index) {
const transitioner = this._getTransitioner(prev !== next ? prev : false, next);
transitioner.translate(percent);
return transitioner;
},
_getTransitioner(prev = this.prevIndex, next = this.index, dir = this.dir || 1, options = this.transitionOptions) {
return new this.Transitioner(
isNumber(prev) ? this.slides[prev] : prev,
isNumber(next) ? this.slides[next] : next,
dir * (isRtl ? -1 : 1),
options
);
}
}
};
function getDirection(index, prevIndex) {
return index === 'next'
? 1
: index === 'previous'
? -1
: index < prevIndex
? -1
: 1;
}
export function speedUp(x) {
return .5 * x + 300; // parabola through (400,500; 600,600; 1800,1200)
}

View File

@@ -0,0 +1,53 @@
import Animations from './internal/slideshow-animations';
import Transitioner from './internal/slideshow-transitioner';
import Slider from './slider.js';
import {addClass, assign, removeClass} from 'uikit-util';
export default {
mixins: [Slider],
props: {
animation: String
},
data: {
animation: 'slide',
clsActivated: 'uk-transition-active',
Animations,
Transitioner
},
computed: {
animation({animation, Animations}) {
return assign(Animations[animation] || Animations.slide, {name: animation});
},
transitionOptions() {
return {animation: this.animation};
}
},
events: {
'itemshow itemhide itemshown itemhidden'({target}) {
this.$update(target);
},
beforeitemshow({target}) {
addClass(target, this.clsActive);
},
itemshown({target}) {
addClass(target, this.clsActivated);
},
itemhidden({target}) {
removeClass(target, this.clsActive, this.clsActivated);
}
}
};

View File

@@ -0,0 +1,180 @@
import {$$, addClass, Animation, assign, css, fastdom, hasClass, height, includes, isBoolean, isFunction, isVisible, noop, Promise, removeClass, toFloat, toggleClass, toNodes, Transition, trigger} from 'uikit-util';
export default {
props: {
cls: Boolean,
animation: 'list',
duration: Number,
origin: String,
transition: String
},
data: {
cls: false,
animation: [false],
duration: 200,
origin: false,
transition: 'linear',
clsEnter: 'uk-togglabe-enter',
clsLeave: 'uk-togglabe-leave',
initProps: {
overflow: '',
height: '',
paddingTop: '',
paddingBottom: '',
marginTop: '',
marginBottom: ''
},
hideProps: {
overflow: 'hidden',
height: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0
}
},
computed: {
hasAnimation({animation}) {
return !!animation[0];
},
hasTransition({animation}) {
return this.hasAnimation && animation[0] === true;
}
},
methods: {
toggleElement(targets, toggle, animate) {
return new Promise(resolve =>
Promise.all(toNodes(targets).map(el => {
const show = isBoolean(toggle) ? toggle : !this.isToggled(el);
if (!trigger(el, `before${show ? 'show' : 'hide'}`, [this])) {
return Promise.reject();
}
const promise = (
isFunction(animate)
? animate
: animate === false || !this.hasAnimation
? this._toggle
: this.hasTransition
? toggleHeight(this)
: toggleAnimation(this)
)(el, show);
const cls = show ? this.clsEnter : this.clsLeave;
addClass(el, cls);
trigger(el, show ? 'show' : 'hide', [this]);
const done = () => {
removeClass(el, cls);
trigger(el, show ? 'shown' : 'hidden', [this]);
this.$update(el);
};
return promise ? promise.then(done, () => {
removeClass(el, cls);
return Promise.reject();
}) : done();
})).then(resolve, noop)
);
},
isToggled(el = this.$el) {
[el] = toNodes(el);
return hasClass(el, this.clsEnter)
? true
: hasClass(el, this.clsLeave)
? false
: this.cls
? hasClass(el, this.cls.split(' ')[0])
: isVisible(el);
},
_toggle(el, toggled) {
if (!el) {
return;
}
toggled = Boolean(toggled);
let changed;
if (this.cls) {
changed = includes(this.cls, ' ') || toggled !== hasClass(el, this.cls);
changed && toggleClass(el, this.cls, includes(this.cls, ' ') ? undefined : toggled);
} else {
changed = toggled === el.hidden;
changed && (el.hidden = !toggled);
}
$$('[autofocus]', el).some(el => isVisible(el) ? el.focus() || true : el.blur());
if (changed) {
trigger(el, 'toggled', [toggled, this]);
this.$update(el);
}
}
}
};
export function toggleHeight({isToggled, duration, initProps, hideProps, transition, _toggle}) {
return (el, show) => {
const inProgress = Transition.inProgress(el);
const inner = el.hasChildNodes ? toFloat(css(el.firstElementChild, 'marginTop')) + toFloat(css(el.lastElementChild, 'marginBottom')) : 0;
const currentHeight = isVisible(el) ? height(el) + (inProgress ? 0 : inner) : 0;
Transition.cancel(el);
if (!isToggled(el)) {
_toggle(el, true);
}
height(el, '');
// Update child components first
fastdom.flush();
const endHeight = height(el) + (inProgress ? 0 : inner);
height(el, currentHeight);
return (show
? Transition.start(el, assign({}, initProps, {overflow: 'hidden', height: endHeight}), Math.round(duration * (1 - currentHeight / endHeight)), transition)
: Transition.start(el, hideProps, Math.round(duration * (currentHeight / endHeight)), transition).then(() => _toggle(el, false))
).then(() => css(el, initProps));
};
}
function toggleAnimation(cmp) {
return (el, show) => {
Animation.cancel(el);
const {animation, duration, _toggle} = cmp;
if (show) {
_toggle(el, true);
return Animation.in(el, animation[0], duration, cmp.origin);
}
return Animation.out(el, animation[1] || animation[0], duration, cmp.origin).then(() => _toggle(el, false));
};
}

View File

@@ -0,0 +1,17 @@
import UIkit from './api/index';
import Core from './core/core';
import boot from './api/boot';
import * as components from './core/index';
import {each} from 'uikit-util';
// register components
each(components, (component, name) =>
UIkit.component(name, component)
);
// core functionality
UIkit.use(Core);
boot(UIkit);
export default UIkit;

View File

@@ -0,0 +1,9 @@
import UIkit from './uikit-core';
import * as components from './components/index';
import {each} from 'uikit-util';
each(components, (component, name) =>
UIkit.component(name, component)
);
export default UIkit;

View File

@@ -0,0 +1,89 @@
import {on} from './event';
import {Promise} from './promise';
import {assign, isString, noop} from './lang';
export function ajax(url, options) {
const env = assign({
data: null,
method: 'GET',
headers: {},
xhr: new XMLHttpRequest(),
beforeSend: noop,
responseType: ''
}, options);
return Promise.resolve()
.then(() => env.beforeSend(env))
.then(() => send(url, env));
}
function send(url, env) {
return new Promise((resolve, reject) => {
let {xhr} = env;
for (const prop in env) {
if (prop in xhr) {
try {
xhr[prop] = env[prop];
} catch (e) {}
}
}
xhr.open(env.method.toUpperCase(), url);
for (const header in env.headers) {
xhr.setRequestHeader(header, env.headers[header]);
}
on(xhr, 'load', () => {
if (xhr.status === 0 || xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
// IE 11 does not support responseType 'json'
if (env.responseType === 'json' && isString(xhr.response)) {
xhr = assign(copyXhr(xhr), {response: JSON.parse(xhr.response)});
}
resolve(xhr);
} else {
reject(assign(Error(xhr.statusText), {
xhr,
status: xhr.status
}));
}
});
on(xhr, 'error', () => reject(assign(Error('Network Error'), {xhr})));
on(xhr, 'timeout', () => reject(assign(Error('Network Timeout'), {xhr})));
xhr.send(env.data);
});
}
export function getImage(src, srcset, sizes) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onerror = e => reject(e);
img.onload = () => resolve(img);
sizes && (img.sizes = sizes);
srcset && (img.srcset = srcset);
img.src = src;
});
}
function copyXhr(source) {
const target = {};
for (const key in source) {
target[key] = source[key];
}
return target;
}

View File

@@ -0,0 +1,115 @@
import {attr} from './attr';
import {Promise} from './promise';
import {once, trigger} from './event';
import {css, propName} from './style';
import {assign, startsWith, toNodes} from './lang';
import {addClass, hasClass, removeClass, removeClasses} from './class';
export function transition(element, props, duration = 400, timing = 'linear') {
return Promise.all(toNodes(element).map(element =>
new Promise((resolve, reject) => {
for (const name in props) {
const value = css(element, name);
if (value === '') {
css(element, name, value);
}
}
const timer = setTimeout(() => trigger(element, 'transitionend'), duration);
once(element, 'transitionend transitioncanceled', ({type}) => {
clearTimeout(timer);
removeClass(element, 'uk-transition');
css(element, {
transitionProperty: '',
transitionDuration: '',
transitionTimingFunction: ''
});
type === 'transitioncanceled' ? reject() : resolve(element);
}, {self: true});
addClass(element, 'uk-transition');
css(element, assign({
transitionProperty: Object.keys(props).map(propName).join(','),
transitionDuration: `${duration}ms`,
transitionTimingFunction: timing
}, props));
})
));
}
export const Transition = {
start: transition,
stop(element) {
trigger(element, 'transitionend');
return Promise.resolve();
},
cancel(element) {
trigger(element, 'transitioncanceled');
},
inProgress(element) {
return hasClass(element, 'uk-transition');
}
};
const animationPrefix = 'uk-animation-';
export function animate(element, animation, duration = 200, origin, out) {
return Promise.all(toNodes(element).map(element =>
new Promise((resolve, reject) => {
trigger(element, 'animationcanceled');
const timer = setTimeout(() => trigger(element, 'animationend'), duration);
once(element, 'animationend animationcanceled', ({type}) => {
clearTimeout(timer);
type === 'animationcanceled' ? reject() : resolve(element);
css(element, 'animationDuration', '');
removeClasses(element, `${animationPrefix}\\S*`);
}, {self: true});
css(element, 'animationDuration', `${duration}ms`);
addClass(element, animation, animationPrefix + (out ? 'leave' : 'enter'));
if (startsWith(animation, animationPrefix)) {
origin && addClass(element, `uk-transform-origin-${origin}`);
out && addClass(element, `${animationPrefix}reverse`);
}
})
));
}
const inProgress = new RegExp(`${animationPrefix}(enter|leave)`);
export const Animation = {
in: animate,
out(element, animation, duration, origin) {
return animate(element, animation, duration, origin, true);
},
inProgress(element) {
return inProgress.test(attr(element, 'class'));
},
cancel(element) {
trigger(element, 'animationcanceled');
}
};

View File

@@ -0,0 +1,51 @@
import {isFunction, isObject, isUndefined, toNode, toNodes} from './lang';
export function attr(element, name, value) {
if (isObject(name)) {
for (const key in name) {
attr(element, key, name[key]);
}
return;
}
if (isUndefined(value)) {
element = toNode(element);
return element && element.getAttribute(name);
} else {
toNodes(element).forEach(element => {
if (isFunction(value)) {
value = value.call(element, attr(element, name));
}
if (value === null) {
removeAttr(element, name);
} else {
element.setAttribute(name, value);
}
});
}
}
export function hasAttr(element, name) {
return toNodes(element).some(element => element.hasAttribute(name));
}
export function removeAttr(element, name) {
element = toNodes(element);
name.split(' ').forEach(name =>
element.forEach(element =>
element.hasAttribute(name) && element.removeAttribute(name)
)
);
}
export function data(element, attribute) {
for (let i = 0, attrs = [attribute, `data-${attribute}`]; i < attrs.length; i++) {
if (hasAttr(element, attrs[i])) {
return attr(element, attrs[i]);
}
}
}

View File

@@ -0,0 +1,93 @@
import {attr} from './attr';
import {isUndefined, toNodes} from './lang';
export function addClass(element, ...args) {
apply(element, args, 'add');
}
export function removeClass(element, ...args) {
apply(element, args, 'remove');
}
export function removeClasses(element, cls) {
attr(element, 'class', value => (value || '').replace(new RegExp(`\\b${cls}\\b`, 'g'), ''));
}
export function replaceClass(element, ...args) {
args[0] && removeClass(element, args[0]);
args[1] && addClass(element, args[1]);
}
export function hasClass(element, cls) {
[cls] = getClasses(cls);
const nodes = toNodes(element);
for (let n = 0; n < nodes.length; n++) {
if (cls && nodes[n].classList.contains(cls)) {
return true;
}
}
return false;
}
export function toggleClass(element, cls, force) {
cls = getClasses(cls);
const nodes = toNodes(element);
for (let n = 0; n < nodes.length; n++) {
const list = nodes[n].classList;
for (let i = 0; i < cls.length; i++) {
if (isUndefined(force)) {
list.toggle(cls[i]);
} else if (supports.Force) {
list.toggle(cls[i], !!force);
} else {
list[force ? 'add' : 'remove'](cls[i]);
}
}
}
}
function apply(element, args, fn) {
args = args.reduce((args, arg) => args.concat(getClasses(arg)), []);
const nodes = toNodes(element);
for (let n = 0; n < nodes.length; n++) {
if (supports.Multiple) {
nodes[n].classList[fn](...args);
} else {
args.forEach(cls => nodes[n].classList[fn](cls));
}
}
}
function getClasses(str) {
return String(str).split(/\s|,/).filter(Boolean);
}
// IE 11
let supports = {
get Multiple() {
return this.get('Multiple');
},
get Force() {
return this.get('Force');
},
get(key) {
const {classList} = document.createElement('_');
classList.add('a', 'b');
classList.toggle('c', false);
supports = {
Multiple: classList.contains('b'),
Force: !classList.contains('c')
};
return supports[key];
}
};

View File

@@ -0,0 +1,173 @@
import {css} from './style';
import {each, endsWith, isDocument, isElement, isNumeric, isUndefined, isWindow, toFloat, toNode, toWindow, ucfirst} from './lang';
const dirs = {
width: ['left', 'right'],
height: ['top', 'bottom']
};
export function dimensions(element) {
const rect = isElement(element)
? toNode(element).getBoundingClientRect()
: {height: height(element), width: width(element), top: 0, left: 0};
return {
height: rect.height,
width: rect.width,
top: rect.top,
left: rect.left,
bottom: rect.top + rect.height,
right: rect.left + rect.width
};
}
export function offset(element, coordinates) {
const currentOffset = dimensions(element);
if (element) {
const {pageYOffset, pageXOffset} = toWindow(element);
const offsetBy = {height: pageYOffset, width: pageXOffset};
for (const dir in dirs) {
for (const i in dirs[dir]) {
currentOffset[dirs[dir][i]] += offsetBy[dir];
}
}
}
if (!coordinates) {
return currentOffset;
}
const pos = css(element, 'position');
each(css(element, ['left', 'top']), (value, prop) =>
css(element, prop, coordinates[prop]
- currentOffset[prop]
+ toFloat(pos === 'absolute' && value === 'auto'
? position(element)[prop]
: value)
)
);
}
export function position(element) {
let {top, left} = offset(element);
const {ownerDocument: {body, documentElement}, offsetParent} = toNode(element);
let parent = offsetParent || documentElement;
while (parent && (parent === body || parent === documentElement) && css(parent, 'position') === 'static') {
parent = parent.parentNode;
}
if (isElement(parent)) {
const parentOffset = offset(parent);
top -= parentOffset.top + toFloat(css(parent, 'borderTopWidth'));
left -= parentOffset.left + toFloat(css(parent, 'borderLeftWidth'));
}
return {
top: top - toFloat(css(element, 'marginTop')),
left: left - toFloat(css(element, 'marginLeft'))
};
}
export function offsetPosition(element) {
const offset = [0, 0];
element = toNode(element);
do {
offset[0] += element.offsetTop;
offset[1] += element.offsetLeft;
if (css(element, 'position') === 'fixed') {
const win = toWindow(element);
offset[0] += win.pageYOffset;
offset[1] += win.pageXOffset;
return offset;
}
} while ((element = element.offsetParent));
return offset;
}
export const height = dimension('height');
export const width = dimension('width');
function dimension(prop) {
const propName = ucfirst(prop);
return (element, value) => {
if (isUndefined(value)) {
if (isWindow(element)) {
return element[`inner${propName}`];
}
if (isDocument(element)) {
const doc = element.documentElement;
return Math.max(doc[`offset${propName}`], doc[`scroll${propName}`]);
}
element = toNode(element);
value = css(element, prop);
value = value === 'auto' ? element[`offset${propName}`] : toFloat(value) || 0;
return value - boxModelAdjust(element, prop);
} else {
return css(element, prop, !value && value !== 0
? ''
: +value + boxModelAdjust(element, prop) + 'px'
);
}
};
}
export function boxModelAdjust(element, prop, sizing = 'border-box') {
return css(element, 'boxSizing') === sizing
? dirs[prop].map(ucfirst).reduce((value, prop) =>
value
+ toFloat(css(element, `padding${prop}`))
+ toFloat(css(element, `border${prop}Width`))
, 0)
: 0;
}
export function flipPosition(pos) {
for (const dir in dirs) {
for (const i in dirs[dir]) {
if (dirs[dir][i] === pos) {
return dirs[dir][1 - i];
}
}
}
return pos;
}
export function toPx(value, property = 'width', element = window) {
return isNumeric(value)
? +value
: endsWith(value, 'vh')
? percent(height(toWindow(element)), value)
: endsWith(value, 'vw')
? percent(width(toWindow(element)), value)
: endsWith(value, '%')
? percent(dimensions(element)[property], value)
: toFloat(value);
}
function percent(base, value) {
return base * toFloat(value) / 100;
}

View File

@@ -0,0 +1,150 @@
import {once} from './event';
import {parent} from './filter';
import {find, findAll} from './selector';
import {isElement, isString, isUndefined, toNode, toNodes} from './lang';
export function ready(fn) {
if (document.readyState !== 'loading') {
fn();
return;
}
once(document, 'DOMContentLoaded', fn);
}
export function empty(element) {
element = $(element);
element.innerHTML = '';
return element;
}
export function html(parent, html) {
parent = $(parent);
return isUndefined(html)
? parent.innerHTML
: append(parent.hasChildNodes() ? empty(parent) : parent, html);
}
export function prepend(parent, element) {
parent = $(parent);
if (parent.hasChildNodes()) {
return insertNodes(element, element => parent.insertBefore(element, parent.firstChild));
} else {
return append(parent, element);
}
}
export function append(parent, element) {
parent = $(parent);
return insertNodes(element, element => parent.appendChild(element));
}
export function before(ref, element) {
ref = $(ref);
return insertNodes(element, element => ref.parentNode.insertBefore(element, ref));
}
export function after(ref, element) {
ref = $(ref);
return insertNodes(element, element => ref.nextSibling
? before(ref.nextSibling, element)
: append(ref.parentNode, element)
);
}
function insertNodes(element, fn) {
element = isString(element) ? fragment(element) : element;
return element
? 'length' in element
? toNodes(element).map(fn)
: fn(element)
: null;
}
export function remove(element) {
toNodes(element).forEach(element => element.parentNode && element.parentNode.removeChild(element));
}
export function wrapAll(element, structure) {
structure = toNode(before(element, structure));
while (structure.firstChild) {
structure = structure.firstChild;
}
append(structure, element);
return structure;
}
export function wrapInner(element, structure) {
return toNodes(toNodes(element).map(element =>
element.hasChildNodes ? wrapAll(toNodes(element.childNodes), structure) : append(element, structure)
));
}
export function unwrap(element) {
toNodes(element)
.map(parent)
.filter((value, index, self) => self.indexOf(value) === index)
.forEach(parent => {
before(parent, parent.childNodes);
remove(parent);
});
}
const fragmentRe = /^\s*<(\w+|!)[^>]*>/;
const singleTagRe = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;
export function fragment(html) {
const matches = singleTagRe.exec(html);
if (matches) {
return document.createElement(matches[1]);
}
const container = document.createElement('div');
if (fragmentRe.test(html)) {
container.insertAdjacentHTML('beforeend', html.trim());
} else {
container.textContent = html;
}
return container.childNodes.length > 1 ? toNodes(container.childNodes) : container.firstChild;
}
export function apply(node, fn) {
if (!isElement(node)) {
return;
}
fn(node);
node = node.firstElementChild;
while (node) {
const next = node.nextElementSibling;
apply(node, fn);
node = next;
}
}
export function $(selector, context) {
return isHtml(selector)
? toNode(fragment(selector))
: find(selector, context);
}
export function $$(selector, context) {
return isHtml(selector)
? toNodes(fragment(selector))
: findAll(selector, context);
}
function isHtml(str) {
return isString(str) && (str[0] === '<' || str.match(/^\s*</));
}

View File

@@ -0,0 +1,19 @@
/* global DocumentTouch */
import {attr} from './attr';
export const inBrowser = typeof window !== 'undefined';
export const isIE = inBrowser && /msie|trident/i.test(window.navigator.userAgent);
export const isRtl = inBrowser && attr(document.documentElement, 'dir') === 'rtl';
const hasTouchEvents = inBrowser && 'ontouchstart' in window;
const hasPointerEvents = inBrowser && window.PointerEvent;
export const hasTouch = inBrowser && (hasTouchEvents
|| window.DocumentTouch && document instanceof DocumentTouch
|| navigator.maxTouchPoints); // IE >=11
export const pointerDown = hasPointerEvents ? 'pointerdown' : hasTouchEvents ? 'touchstart' : 'mousedown';
export const pointerMove = hasPointerEvents ? 'pointermove' : hasTouchEvents ? 'touchmove' : 'mousemove';
export const pointerUp = hasPointerEvents ? 'pointerup' : hasTouchEvents ? 'touchend' : 'mouseup';
export const pointerEnter = hasPointerEvents ? 'pointerenter' : hasTouchEvents ? '' : 'mouseenter';
export const pointerLeave = hasPointerEvents ? 'pointerleave' : hasTouchEvents ? '' : 'mouseleave';
export const pointerCancel = hasPointerEvents ? 'pointercancel' : 'touchcancel';

View File

@@ -0,0 +1,141 @@
import {isIE} from './env';
import {findAll} from './selector';
import {closest, within} from './filter';
import {isArray, isBoolean, isFunction, isString, toNode, toNodes} from './lang';
export function on(...args) {
let [targets, type, selector, listener, useCapture] = getArgs(args);
targets = toEventTargets(targets);
if (listener.length > 1) {
listener = detail(listener);
}
if (useCapture && useCapture.self) {
listener = selfFilter(listener);
}
if (selector) {
listener = delegate(selector, listener);
}
useCapture = useCaptureFilter(useCapture);
type.split(' ').forEach(type =>
targets.forEach(target =>
target.addEventListener(type, listener, useCapture)
)
);
return () => off(targets, type, listener, useCapture);
}
export function off(targets, type, listener, useCapture = false) {
useCapture = useCaptureFilter(useCapture);
targets = toEventTargets(targets);
type.split(' ').forEach(type =>
targets.forEach(target =>
target.removeEventListener(type, listener, useCapture)
)
);
}
export function once(...args) {
const [element, type, selector, listener, useCapture, condition] = getArgs(args);
const off = on(element, type, selector, e => {
const result = !condition || condition(e);
if (result) {
off();
listener(e, result);
}
}, useCapture);
return off;
}
export function trigger(targets, event, detail) {
return toEventTargets(targets).reduce((notCanceled, target) =>
notCanceled && target.dispatchEvent(createEvent(event, true, true, detail))
, true);
}
export function createEvent(e, bubbles = true, cancelable = false, detail) {
if (isString(e)) {
const event = document.createEvent('CustomEvent'); // IE 11
event.initCustomEvent(e, bubbles, cancelable, detail);
e = event;
}
return e;
}
function getArgs(args) {
if (isFunction(args[2])) {
args.splice(2, 0, false);
}
return args;
}
function delegate(selector, listener) {
return e => {
const current = selector[0] === '>'
? findAll(selector, e.currentTarget).reverse().filter(element => within(e.target, element))[0]
: closest(e.target, selector);
if (current) {
e.current = current;
listener.call(this, e);
}
};
}
function detail(listener) {
return e => isArray(e.detail) ? listener(e, ...e.detail) : listener(e);
}
function selfFilter(listener) {
return function (e) {
if (e.target === e.currentTarget || e.target === e.current) {
return listener.call(null, e);
}
};
}
function useCaptureFilter(options) {
return options && isIE && !isBoolean(options)
? !!options.capture
: options;
}
function isEventTarget(target) {
return target && 'addEventListener' in target;
}
function toEventTarget(target) {
return isEventTarget(target) ? target : toNode(target);
}
export function toEventTargets(target) {
return isArray(target)
? target.map(toEventTarget).filter(Boolean)
: isString(target)
? findAll(target)
: isEventTarget(target)
? [target]
: toNodes(target);
}
export function isTouch(e) {
return e.pointerType === 'touch' || !!e.touches;
}
export function getEventPos(e) {
const {touches, changedTouches} = e;
const {clientX: x, clientY: y} = touches && touches[0] || changedTouches && changedTouches[0] || e;
return {x, y};
}

View File

@@ -0,0 +1,75 @@
import {Promise} from './promise';
/*
Based on:
Copyright (c) 2016 Wilson Page wilsonpage@me.com
https://github.com/wilsonpage/fastdom
*/
export const fastdom = {
reads: [],
writes: [],
read(task) {
this.reads.push(task);
scheduleFlush();
return task;
},
write(task) {
this.writes.push(task);
scheduleFlush();
return task;
},
clear(task) {
remove(this.reads, task);
remove(this.writes, task);
},
flush
};
function flush(recursion = 1) {
runTasks(fastdom.reads);
runTasks(fastdom.writes.splice(0));
fastdom.scheduled = false;
if (fastdom.reads.length || fastdom.writes.length) {
scheduleFlush(recursion + 1);
}
}
const RECURSION_LIMIT = 4;
function scheduleFlush(recursion) {
if (fastdom.scheduled) {
return;
}
fastdom.scheduled = true;
if (recursion && recursion < RECURSION_LIMIT) {
Promise.resolve().then(() => flush(recursion));
} else {
requestAnimationFrame(() => flush());
}
}
function runTasks(tasks) {
let task;
while ((task = tasks.shift())) {
try {
task();
} catch (e) {
console.error(e);
}
}
}
function remove(array, item) {
const index = array.indexOf(item);
return ~index && array.splice(index, 1);
}

View File

@@ -0,0 +1,109 @@
import {inBrowser} from './env';
import {isDocument, isElement, isString, noop, startsWith, toNode, toNodes} from './lang';
const voidElements = {
area: true,
base: true,
br: true,
col: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
menuitem: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
};
export function isVoidElement(element) {
return toNodes(element).some(element => voidElements[element.tagName.toLowerCase()]);
}
export function isVisible(element) {
return toNodes(element).some(element => element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
export const selInput = 'input,select,textarea,button';
export function isInput(element) {
return toNodes(element).some(element => matches(element, selInput));
}
export const selFocusable = `${selInput},a[href],[tabindex]`;
export function isFocusable(element) {
return matches(element, selFocusable);
}
export function parent(element) {
element = toNode(element);
return element && isElement(element.parentNode) && element.parentNode;
}
export function filter(element, selector) {
return toNodes(element).filter(element => matches(element, selector));
}
const elProto = inBrowser ? Element.prototype : {};
const matchesFn = elProto.matches || elProto.webkitMatchesSelector || elProto.msMatchesSelector || noop;
export function matches(element, selector) {
return toNodes(element).some(element => matchesFn.call(element, selector));
}
const closestFn = elProto.closest || function (selector) {
let ancestor = this;
do {
if (matches(ancestor, selector)) {
return ancestor;
}
} while ((ancestor = parent(ancestor)));
};
export function closest(element, selector) {
if (startsWith(selector, '>')) {
selector = selector.slice(1);
}
return isElement(element)
? closestFn.call(element, selector)
: toNodes(element).map(element => closest(element, selector)).filter(Boolean);
}
export function within(element, selector) {
return !isString(selector)
? element === selector || (isDocument(selector)
? selector.documentElement
: toNode(selector)).contains(toNode(element)) // IE 11 document does not implement contains
: matches(element, selector) || !!closest(element, selector);
}
export function parents(element, selector) {
const elements = [];
while ((element = parent(element))) {
if (!selector || matches(element, selector)) {
elements.push(element);
}
}
return elements;
}
export function children(element, selector) {
element = toNode(element);
const children = element ? toNodes(element.children) : [];
return selector ? filter(children, selector) : children;
}
export function index(element, ref) {
return ref
? toNodes(element).indexOf(toNode(ref))
: children(parent(element)).indexOf(element);
}

View File

@@ -0,0 +1,19 @@
export * from './ajax';
export * from './animation';
export * from './attr';
export * from './class';
export * from './dimensions';
export * from './dom';
export * from './env';
export * from './event';
export * from './fastdom';
export * from './filter';
export * from './lang';
export * from './mouse';
export * from './options';
export * from './player';
export * from './position';
export * from './promise';
export * from './selector';
export * from './style';
export * from './viewport';

View File

@@ -0,0 +1,327 @@
const objPrototype = Object.prototype;
const {hasOwnProperty} = objPrototype;
export function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
}
const hyphenateRe = /\B([A-Z])/g;
export const hyphenate = memoize(str => str
.replace(hyphenateRe, '-$1')
.toLowerCase()
);
const camelizeRe = /-(\w)/g;
export const camelize = memoize(str =>
str.replace(camelizeRe, toUpper)
);
export const ucfirst = memoize(str =>
str.length ? toUpper(null, str.charAt(0)) + str.slice(1) : ''
);
function toUpper(_, c) {
return c ? c.toUpperCase() : '';
}
const strPrototype = String.prototype;
const startsWithFn = strPrototype.startsWith || function (search) { return this.lastIndexOf(search, 0) === 0; };
export function startsWith(str, search) {
return startsWithFn.call(str, search);
}
const endsWithFn = strPrototype.endsWith || function (search) { return this.substr(-search.length) === search; };
export function endsWith(str, search) {
return endsWithFn.call(str, search);
}
const arrPrototype = Array.prototype;
const includesFn = function (search, i) { return !!~this.indexOf(search, i); };
const includesStr = strPrototype.includes || includesFn;
const includesArray = arrPrototype.includes || includesFn;
export function includes(obj, search) {
return obj && (isString(obj) ? includesStr : includesArray).call(obj, search);
}
const findIndexFn = arrPrototype.findIndex || function (predicate) {
for (let i = 0; i < this.length; i++) {
if (predicate.call(arguments[1], this[i], i, this)) {
return i;
}
}
return -1;
};
export function findIndex(array, predicate) {
return findIndexFn.call(array, predicate);
}
export const {isArray} = Array;
export function isFunction(obj) {
return typeof obj === 'function';
}
export function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
const {toString} = objPrototype;
export function isPlainObject(obj) {
return toString.call(obj) === '[object Object]';
}
export function isWindow(obj) {
return isObject(obj) && obj === obj.window;
}
export function isDocument(obj) {
return nodeType(obj) === 9;
}
export function isNode(obj) {
return nodeType(obj) >= 1;
}
export function isElement(obj) {
return nodeType(obj) === 1;
}
function nodeType(obj) {
return !isWindow(obj) && isObject(obj) && obj.nodeType;
}
export function isBoolean(value) {
return typeof value === 'boolean';
}
export function isString(value) {
return typeof value === 'string';
}
export function isNumber(value) {
return typeof value === 'number';
}
export function isNumeric(value) {
return isNumber(value) || isString(value) && !isNaN(value - parseFloat(value));
}
export function isEmpty(obj) {
return !(isArray(obj)
? obj.length
: isObject(obj)
? Object.keys(obj).length
: false
);
}
export function isUndefined(value) {
return value === void 0;
}
export function toBoolean(value) {
return isBoolean(value)
? value
: value === 'true' || value === '1' || value === ''
? true
: value === 'false' || value === '0'
? false
: value;
}
export function toNumber(value) {
const number = Number(value);
return isNaN(number) ? false : number;
}
export function toFloat(value) {
return parseFloat(value) || 0;
}
export const toArray = Array.from || (value => arrPrototype.slice.call(value));
export function toNode(element) {
return toNodes(element)[0];
}
export function toNodes(element) {
return element && (isNode(element) ? [element] : toArray(element).filter(isNode)) || [];
}
export function toWindow(element) {
if (isWindow(element)) {
return element;
}
element = toNode(element);
return element
? (isDocument(element)
? element
: element.ownerDocument
).defaultView
: window;
}
export function toMs(time) {
return !time
? 0
: endsWith(time, 'ms')
? toFloat(time)
: toFloat(time) * 1000;
}
export function isEqual(value, other) {
return value === other
|| isObject(value)
&& isObject(other)
&& Object.keys(value).length === Object.keys(other).length
&& each(value, (val, key) => val === other[key]);
}
export function swap(value, a, b) {
return value.replace(
new RegExp(`${a}|${b}`, 'g'),
match => match === a ? b : a
);
}
export const assign = Object.assign || function (target, ...args) {
target = Object(target);
for (let i = 0; i < args.length; i++) {
const source = args[i];
if (source !== null) {
for (const key in source) {
if (hasOwn(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
export function last(array) {
return array[array.length - 1];
}
export function each(obj, cb) {
for (const key in obj) {
if (false === cb(obj[key], key)) {
return false;
}
}
return true;
}
export function sortBy(array, prop) {
return array.slice().sort(({[prop]: propA = 0}, {[prop]: propB = 0}) =>
propA > propB
? 1
: propB > propA
? -1
: 0
);
}
export function uniqueBy(array, prop) {
const seen = new Set();
return array.filter(({[prop]: check}) => seen.has(check)
? false
: seen.add(check) || true // IE 11 does not return the Set object
);
}
export function clamp(number, min = 0, max = 1) {
return Math.min(Math.max(toNumber(number) || 0, min), max);
}
export function noop() {}
export function intersectRect(...rects) {
return [['bottom', 'top'], ['right', 'left']].every(([minProp, maxProp]) =>
Math.min(...rects.map(({[minProp]: min}) => min)) - Math.max(...rects.map(({[maxProp]: max}) => max)) > 0
);
}
export function pointInRect(point, rect) {
return point.x <= rect.right &&
point.x >= rect.left &&
point.y <= rect.bottom &&
point.y >= rect.top;
}
export const Dimensions = {
ratio(dimensions, prop, value) {
const aProp = prop === 'width' ? 'height' : 'width';
return {
[aProp]: dimensions[prop] ? Math.round(value * dimensions[aProp] / dimensions[prop]) : dimensions[aProp],
[prop]: value
};
},
contain(dimensions, maxDimensions) {
dimensions = assign({}, dimensions);
each(dimensions, (_, prop) => dimensions = dimensions[prop] > maxDimensions[prop]
? this.ratio(dimensions, prop, maxDimensions[prop])
: dimensions
);
return dimensions;
},
cover(dimensions, maxDimensions) {
dimensions = this.contain(dimensions, maxDimensions);
each(dimensions, (_, prop) => dimensions = dimensions[prop] < maxDimensions[prop]
? this.ratio(dimensions, prop, maxDimensions[prop])
: dimensions
);
return dimensions;
}
};
export function getIndex(i, elements, current = 0, finite = false) {
elements = toNodes(elements);
const {length} = elements;
if (!length) {
return -1;
}
i = isNumeric(i)
? toNumber(i)
: i === 'next'
? current + 1
: i === 'previous'
? current - 1
: elements.indexOf(toNode(i));
if (finite) {
return clamp(i, 0, length - 1);
}
i %= length;
return i < 0 ? i + length : i;
}
export function memoize(fn) {
const cache = Object.create(null);
return key => cache[key] || (cache[key] = fn(key));
}

View File

@@ -0,0 +1,81 @@
import {getEventPos, on} from './event';
import {last, pointInRect} from './lang';
export function MouseTracker() {}
MouseTracker.prototype = {
positions: [],
init() {
this.positions = [];
let position;
this.unbind = on(document, 'mousemove', e => position = getEventPos(e));
this.interval = setInterval(() => {
if (!position) {
return;
}
this.positions.push(position);
if (this.positions.length > 5) {
this.positions.shift();
}
}, 50);
},
cancel() {
this.unbind && this.unbind();
this.interval && clearInterval(this.interval);
},
movesTo(target) {
if (this.positions.length < 2) {
return false;
}
const p = target.getBoundingClientRect();
const {left, right, top, bottom} = p;
const [prevPosition] = this.positions;
const position = last(this.positions);
const path = [prevPosition, position];
if (pointInRect(position, p)) {
return false;
}
const diagonals = [[{x: left, y: top}, {x: right, y: bottom}], [{x: left, y: bottom}, {x: right, y: top}]];
return diagonals.some(diagonal => {
const intersection = intersect(path, diagonal);
return intersection && pointInRect(intersection, p);
});
}
};
// Inspired by http://paulbourke.net/geometry/pointlineplane/
function intersect([{x: x1, y: y1}, {x: x2, y: y2}], [{x: x3, y: y3}, {x: x4, y: y4}]) {
const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
// Lines are parallel
if (denominator === 0) {
return false;
}
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
if (ua < 0) {
return false;
}
// Return an object with the x and y coordinates of the intersection
return {x: x1 + ua * (x2 - x1), y: y1 + ua * (y2 - y1)};
}

View File

@@ -0,0 +1,155 @@
import {assign, hasOwn, includes, isArray, isFunction, isUndefined, sortBy, startsWith} from './lang';
const strats = {};
strats.events =
strats.created =
strats.beforeConnect =
strats.connected =
strats.beforeDisconnect =
strats.disconnected =
strats.destroy = concatStrat;
// args strategy
strats.args = function (parentVal, childVal) {
return childVal !== false && concatStrat(childVal || parentVal);
};
// update strategy
strats.update = function (parentVal, childVal) {
return sortBy(concatStrat(parentVal, isFunction(childVal) ? {read: childVal} : childVal), 'order');
};
// property strategy
strats.props = function (parentVal, childVal) {
if (isArray(childVal)) {
childVal = childVal.reduce((value, key) => {
value[key] = String;
return value;
}, {});
}
return strats.methods(parentVal, childVal);
};
// extend strategy
strats.computed =
strats.methods = function (parentVal, childVal) {
return childVal
? parentVal
? assign({}, parentVal, childVal)
: childVal
: parentVal;
};
// data strategy
strats.data = function (parentVal, childVal, vm) {
if (!vm) {
if (!childVal) {
return parentVal;
}
if (!parentVal) {
return childVal;
}
return function (vm) {
return mergeFnData(parentVal, childVal, vm);
};
}
return mergeFnData(parentVal, childVal, vm);
};
function mergeFnData(parentVal, childVal, vm) {
return strats.computed(
isFunction(parentVal)
? parentVal.call(vm, vm)
: parentVal,
isFunction(childVal)
? childVal.call(vm, vm)
: childVal
);
}
// concat strategy
function concatStrat(parentVal, childVal) {
parentVal = parentVal && !isArray(parentVal) ? [parentVal] : parentVal;
return childVal
? parentVal
? parentVal.concat(childVal)
: isArray(childVal)
? childVal
: [childVal]
: parentVal;
}
// default strategy
function defaultStrat(parentVal, childVal) {
return isUndefined(childVal) ? parentVal : childVal;
}
export function mergeOptions(parent, child, vm) {
const options = {};
if (isFunction(child)) {
child = child.options;
}
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
for (const key in parent) {
mergeKey(key);
}
for (const key in child) {
if (!hasOwn(parent, key)) {
mergeKey(key);
}
}
function mergeKey(key) {
options[key] = (strats[key] || defaultStrat)(parent[key], child[key], vm);
}
return options;
}
export function parseOptions(options, args = []) {
try {
return !options
? {}
: startsWith(options, '{')
? JSON.parse(options)
: args.length && !includes(options, ':')
? ({[args[0]]: options})
: options.split(';').reduce((options, option) => {
const [key, value] = option.split(/:(.*)/);
if (key && !isUndefined(value)) {
options[key.trim()] = value.trim();
}
return options;
}, {});
} catch (e) {
return {};
}
}

View File

@@ -0,0 +1,107 @@
import {once} from './event';
import {Promise} from './promise';
import {assign, includes, noop} from './lang';
export function play(el) {
if (isIFrame(el)) {
call(el, {func: 'playVideo', method: 'play'});
}
if (isHTML5(el)) {
try {
el.play().catch(noop);
} catch (e) {}
}
}
export function pause(el) {
if (isIFrame(el)) {
call(el, {func: 'pauseVideo', method: 'pause'});
}
if (isHTML5(el)) {
el.pause();
}
}
export function mute(el) {
if (isIFrame(el)) {
call(el, {func: 'mute', method: 'setVolume', value: 0});
}
if (isHTML5(el)) {
el.muted = true;
}
}
export function isVideo(el) {
return isHTML5(el) || isIFrame(el);
}
function isHTML5(el) {
return el && el.tagName === 'VIDEO';
}
function isIFrame(el) {
return el && el.tagName === 'IFRAME' && (isYoutube(el) || isVimeo(el));
}
function isYoutube(el) {
return !!el.src.match(/\/\/.*?youtube(-nocookie)?\.[a-z]+\/(watch\?v=[^&\s]+|embed)|youtu\.be\/.*/);
}
function isVimeo(el) {
return !!el.src.match(/vimeo\.com\/video\/.*/);
}
function call(el, cmd) {
enableApi(el).then(() => post(el, cmd));
}
function post(el, cmd) {
try {
el.contentWindow.postMessage(JSON.stringify(assign({event: 'command'}, cmd)), '*');
} catch (e) {}
}
const stateKey = '_ukPlayer';
let counter = 0;
function enableApi(el) {
if (el[stateKey]) {
return el[stateKey];
}
const youtube = isYoutube(el);
const vimeo = isVimeo(el);
const id = ++counter;
let poller;
return el[stateKey] = new Promise(resolve => {
youtube && once(el, 'load', () => {
const listener = () => post(el, {event: 'listening', id});
poller = setInterval(listener, 100);
listener();
});
once(window, 'message', resolve, false, ({data}) => {
try {
data = JSON.parse(data);
return data && (youtube && data.id === id && data.event === 'onReady' || vimeo && Number(data.player_id) === id);
} catch (e) {}
});
el.src = `${el.src}${includes(el.src, '?') ? '&' : '?'}${youtube ? 'enablejsapi=1' : `api=1&player_id=${id}`}`;
}).then(() => clearInterval(poller));
}

View File

@@ -0,0 +1,148 @@
import {offset} from './dimensions';
import {each, endsWith, includes, toFloat} from './lang';
import {getViewport, scrollParents} from './viewport';
const dirs = {
width: ['x', 'left', 'right'],
height: ['y', 'top', 'bottom']
};
export function positionAt(element, target, elAttach, targetAttach, elOffset, targetOffset, flip, boundary) {
elAttach = getPos(elAttach);
targetAttach = getPos(targetAttach);
const flipped = {element: elAttach, target: targetAttach};
if (!element || !target) {
return flipped;
}
const dim = offset(element);
const targetDim = offset(target);
const position = targetDim;
moveTo(position, elAttach, dim, -1);
moveTo(position, targetAttach, targetDim, 1);
elOffset = getOffsets(elOffset, dim.width, dim.height);
targetOffset = getOffsets(targetOffset, targetDim.width, targetDim.height);
elOffset['x'] += targetOffset['x'];
elOffset['y'] += targetOffset['y'];
position.left += elOffset['x'];
position.top += elOffset['y'];
if (flip) {
let boundaries = scrollParents(element).map(getViewport);
if (boundary && !includes(boundaries, boundary)) {
boundaries.unshift(boundary);
}
boundaries = boundaries.map(el => offset(el));
each(dirs, ([dir, align, alignFlip], prop) => {
if (!(flip === true || includes(flip, dir))) {
return;
}
boundaries.some(boundary => {
const elemOffset = elAttach[dir] === align
? -dim[prop]
: elAttach[dir] === alignFlip
? dim[prop]
: 0;
const targetOffset = targetAttach[dir] === align
? targetDim[prop]
: targetAttach[dir] === alignFlip
? -targetDim[prop]
: 0;
if (position[align] < boundary[align] || position[align] + dim[prop] > boundary[alignFlip]) {
const centerOffset = dim[prop] / 2;
const centerTargetOffset = targetAttach[dir] === 'center' ? -targetDim[prop] / 2 : 0;
return elAttach[dir] === 'center' && (
apply(centerOffset, centerTargetOffset)
|| apply(-centerOffset, -centerTargetOffset)
) || apply(elemOffset, targetOffset);
}
function apply(elemOffset, targetOffset) {
const newVal = toFloat((position[align] + elemOffset + targetOffset - elOffset[dir] * 2).toFixed(4));
if (newVal >= boundary[align] && newVal + dim[prop] <= boundary[alignFlip]) {
position[align] = newVal;
['element', 'target'].forEach(el => {
flipped[el][dir] = !elemOffset
? flipped[el][dir]
: flipped[el][dir] === dirs[prop][1]
? dirs[prop][2]
: dirs[prop][1];
});
return true;
}
}
});
});
}
offset(element, position);
return flipped;
}
function moveTo(position, attach, dim, factor) {
each(dirs, ([dir, align, alignFlip], prop) => {
if (attach[dir] === alignFlip) {
position[align] += dim[prop] * factor;
} else if (attach[dir] === 'center') {
position[align] += dim[prop] * factor / 2;
}
});
}
function getPos(pos) {
const x = /left|center|right/;
const y = /top|center|bottom/;
pos = (pos || '').split(' ');
if (pos.length === 1) {
pos = x.test(pos[0])
? pos.concat('center')
: y.test(pos[0])
? ['center'].concat(pos)
: ['center', 'center'];
}
return {
x: x.test(pos[0]) ? pos[0] : 'center',
y: y.test(pos[1]) ? pos[1] : 'center'
};
}
function getOffsets(offsets, width, height) {
const [x, y] = (offsets || '').split(' ');
return {
x: x ? toFloat(x) * (endsWith(x, '%') ? width / 100 : 1) : 0,
y: y ? toFloat(y) * (endsWith(y, '%') ? height / 100 : 1) : 0
};
}

View File

@@ -0,0 +1,191 @@
/* global setImmediate */
import {inBrowser} from './env';
import {isFunction, isObject} from './lang';
export const Promise = inBrowser && window.Promise || PromiseFn;
export class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.reject = reject;
this.resolve = resolve;
});
}
}
/**
* Promises/A+ polyfill v1.1.4 (https://github.com/bramstein/promis)
*/
const RESOLVED = 0;
const REJECTED = 1;
const PENDING = 2;
const async = inBrowser && window.setImmediate || setTimeout;
function PromiseFn(executor) {
this.state = PENDING;
this.value = undefined;
this.deferred = [];
const promise = this;
try {
executor(
x => {
promise.resolve(x);
},
r => {
promise.reject(r);
}
);
} catch (e) {
promise.reject(e);
}
}
PromiseFn.reject = function (r) {
return new PromiseFn((resolve, reject) => {
reject(r);
});
};
PromiseFn.resolve = function (x) {
return new PromiseFn((resolve, reject) => {
resolve(x);
});
};
PromiseFn.all = function all(iterable) {
return new PromiseFn((resolve, reject) => {
const result = [];
let count = 0;
if (iterable.length === 0) {
resolve(result);
}
function resolver(i) {
return function (x) {
result[i] = x;
count += 1;
if (count === iterable.length) {
resolve(result);
}
};
}
for (let i = 0; i < iterable.length; i += 1) {
PromiseFn.resolve(iterable[i]).then(resolver(i), reject);
}
});
};
PromiseFn.race = function race(iterable) {
return new PromiseFn((resolve, reject) => {
for (let i = 0; i < iterable.length; i += 1) {
PromiseFn.resolve(iterable[i]).then(resolve, reject);
}
});
};
const p = PromiseFn.prototype;
p.resolve = function resolve(x) {
const promise = this;
if (promise.state === PENDING) {
if (x === promise) {
throw new TypeError('Promise settled with itself.');
}
let called = false;
try {
const then = x && x.then;
if (x !== null && isObject(x) && isFunction(then)) {
then.call(
x,
x => {
if (!called) {
promise.resolve(x);
}
called = true;
},
r => {
if (!called) {
promise.reject(r);
}
called = true;
}
);
return;
}
} catch (e) {
if (!called) {
promise.reject(e);
}
return;
}
promise.state = RESOLVED;
promise.value = x;
promise.notify();
}
};
p.reject = function reject(reason) {
const promise = this;
if (promise.state === PENDING) {
if (reason === promise) {
throw new TypeError('Promise settled with itself.');
}
promise.state = REJECTED;
promise.value = reason;
promise.notify();
}
};
p.notify = function notify() {
async(() => {
if (this.state !== PENDING) {
while (this.deferred.length) {
const [onResolved, onRejected, resolve, reject] = this.deferred.shift();
try {
if (this.state === RESOLVED) {
if (isFunction(onResolved)) {
resolve(onResolved.call(undefined, this.value));
} else {
resolve(this.value);
}
} else if (this.state === REJECTED) {
if (isFunction(onRejected)) {
resolve(onRejected.call(undefined, this.value));
} else {
reject(this.value);
}
}
} catch (e) {
reject(e);
}
}
}
});
};
p.then = function then(onResolved, onRejected) {
return new PromiseFn((resolve, reject) => {
this.deferred.push([onResolved, onRejected, resolve, reject]);
this.notify();
});
};
p.catch = function (onRejected) {
return this.then(undefined, onRejected);
};

View File

@@ -0,0 +1,118 @@
import {attr} from './attr';
import {inBrowser} from './env';
import {closest, index, matches, parent} from './filter';
import {isDocument, isString, memoize, toNode, toNodes} from './lang';
export function query(selector, context) {
return find(selector, getContext(selector, context));
}
export function queryAll(selector, context) {
return findAll(selector, getContext(selector, context));
}
function getContext(selector, context = document) {
return isString(selector) && isContextSelector(selector) || isDocument(context)
? context
: context.ownerDocument;
}
export function find(selector, context) {
return toNode(_query(selector, context, 'querySelector'));
}
export function findAll(selector, context) {
return toNodes(_query(selector, context, 'querySelectorAll'));
}
function _query(selector, context = document, queryFn) {
if (!selector || !isString(selector)) {
return selector;
}
selector = selector.replace(contextSanitizeRe, '$1 *');
if (isContextSelector(selector)) {
selector = splitSelector(selector).map(selector => {
let ctx = context;
if (selector[0] === '!') {
const selectors = selector.substr(1).trim().split(' ');
ctx = closest(parent(context), selectors[0]);
selector = selectors.slice(1).join(' ').trim();
}
if (selector[0] === '-') {
const selectors = selector.substr(1).trim().split(' ');
const prev = (ctx || context).previousElementSibling;
ctx = matches(prev, selector.substr(1)) ? prev : null;
selector = selectors.slice(1).join(' ');
}
if (!ctx) {
return null;
}
return `${domPath(ctx)} ${selector}`;
}).filter(Boolean).join(',');
context = document;
}
try {
return context[queryFn](selector);
} catch (e) {
return null;
}
}
const contextSelectorRe = /(^|[^\\],)\s*[!>+~-]/;
const contextSanitizeRe = /([!>+~-])(?=\s+[!>+~-]|\s*$)/g;
const isContextSelector = memoize(selector => selector.match(contextSelectorRe));
const selectorRe = /.*?[^\\](?:,|$)/g;
const splitSelector = memoize(selector =>
selector.match(selectorRe).map(selector =>
selector.replace(/,$/, '').trim()
)
);
function domPath(element) {
const names = [];
while (element.parentNode) {
const id = attr(element, 'id');
if (id) {
names.unshift(`#${escape(id)}`);
break;
} else {
let {tagName} = element;
if (tagName !== 'HTML') {
tagName += `:nth-child(${index(element) + 1})`;
}
names.unshift(tagName);
element = element.parentNode;
}
}
return names.join(' > ');
}
const escapeFn = inBrowser && window.CSS && CSS.escape || function (css) { return css.replace(/([^\x7f-\uFFFF\w-])/g, match => `\\${match}`); };
export function escape(css) {
return isString(css) ? escapeFn.call(null, css) : '';
}

View File

@@ -0,0 +1,113 @@
import {isIE} from './env';
import {append, fragment, remove} from './dom';
import {addClass} from './class';
import {each, hyphenate, isArray, isNumber, isNumeric, isObject, isString, isUndefined, memoize, toNodes, toWindow} from './lang';
const cssNumber = {
'animation-iteration-count': true,
'column-count': true,
'fill-opacity': true,
'flex-grow': true,
'flex-shrink': true,
'font-weight': true,
'line-height': true,
'opacity': true,
'order': true,
'orphans': true,
'stroke-dasharray': true,
'stroke-dashoffset': true,
'widows': true,
'z-index': true,
'zoom': true
};
export function css(element, property, value, priority = '') {
return toNodes(element).map(element => {
if (isString(property)) {
property = propName(property);
if (isUndefined(value)) {
return getStyle(element, property);
} else if (!value && !isNumber(value)) {
element.style.removeProperty(property);
} else {
element.style.setProperty(property, isNumeric(value) && !cssNumber[property] ? `${value}px` : value, priority);
}
} else if (isArray(property)) {
const styles = getStyles(element);
return property.reduce((props, property) => {
props[property] = styles[propName(property)];
return props;
}, {});
} else if (isObject(property)) {
priority = value;
each(property, (value, property) => css(element, property, value, priority));
}
return element;
})[0];
}
function getStyles(element, pseudoElt) {
return toWindow(element).getComputedStyle(element, pseudoElt);
}
function getStyle(element, property, pseudoElt) {
return getStyles(element, pseudoElt)[property];
}
const parseCssVar = memoize(name => {
/* usage in css: .uk-name:before { content:"xyz" } */
const element = append(document.documentElement, fragment('<div>'));
addClass(element, `uk-${name}`);
const value = getStyle(element, 'content', ':before');
remove(element);
return value;
});
const propertyRe = /^\s*(["'])?(.*?)\1\s*$/;
export function getCssVar(name) {
return (isIE
? parseCssVar(name)
: getStyles(document.documentElement).getPropertyValue(`--uk-${name}`)
).replace(propertyRe, '$2');
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty
export const propName = memoize(name => vendorPropName(name));
const cssPrefixes = ['webkit', 'moz', 'ms'];
function vendorPropName(name) {
name = hyphenate(name);
const {style} = document.documentElement;
if (name in style) {
return name;
}
let i = cssPrefixes.length, prefixedName;
while (i--) {
prefixedName = `-${cssPrefixes[i]}-${name}`;
if (prefixedName in style) {
return prefixedName;
}
}
}

View File

@@ -0,0 +1,155 @@
import {css} from './style';
import {Promise} from './promise';
import {isVisible, parents} from './filter';
import {offset, offsetPosition} from './dimensions';
import {clamp, findIndex, intersectRect, isDocument, isWindow, toNode, toWindow} from './lang';
export function isInView(element, offsetTop = 0, offsetLeft = 0) {
if (!isVisible(element)) {
return false;
}
return intersectRect(...scrollParents(element).map(parent => {
const {top, left, bottom, right} = offset(getViewport(parent));
return {
top: top - offsetTop,
left: left - offsetLeft,
bottom: bottom + offsetTop,
right: right + offsetLeft
};
}).concat(offset(element)));
}
export function scrollTop(element, top) {
if (isWindow(element) || isDocument(element)) {
element = getScrollingElement(element);
} else {
element = toNode(element);
}
element.scrollTop = top;
}
export function scrollIntoView(element, {offset: offsetBy = 0} = {}) {
const parents = isVisible(element) ? scrollParents(element) : [];
return parents.reduce((fn, scrollElement, i) => {
const {scrollTop, scrollHeight, offsetHeight} = scrollElement;
const maxScroll = scrollHeight - getViewportClientHeight(scrollElement);
const {height: elHeight, top: elTop} = offset(parents[i - 1] || element);
let top = Math.ceil(
elTop
- offset(getViewport(scrollElement)).top
- offsetBy
+ scrollTop
);
if (offsetBy > 0 && offsetHeight < elHeight + offsetBy) {
top += offsetBy;
} else {
offsetBy = 0;
}
if (top > maxScroll) {
offsetBy -= top - maxScroll;
top = maxScroll;
} else if (top < 0) {
offsetBy -= top;
top = 0;
}
return () => scrollTo(scrollElement, top - scrollTop).then(fn);
}, () => Promise.resolve())();
function scrollTo(element, top) {
return new Promise(resolve => {
const scroll = element.scrollTop;
const duration = getDuration(Math.abs(top));
const start = Date.now();
(function step() {
const percent = ease(clamp((Date.now() - start) / duration));
scrollTop(element, scroll + top * percent);
// scroll more if we have not reached our destination
if (percent === 1) {
resolve();
} else {
requestAnimationFrame(step);
}
})();
});
}
function getDuration(dist) {
return 40 * Math.pow(dist, .375);
}
function ease(k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}
}
export function scrolledOver(element, heightOffset = 0) {
if (!isVisible(element)) {
return 0;
}
const [scrollElement] = scrollParents(element, /auto|scroll/, true);
const {scrollHeight, scrollTop} = scrollElement;
const clientHeight = getViewportClientHeight(scrollElement);
const viewportTop = offsetPosition(element)[0] - scrollTop - offsetPosition(scrollElement)[0];
const viewportDist = Math.min(clientHeight, viewportTop + scrollTop);
const top = viewportTop - viewportDist;
const dist = Math.min(
element.offsetHeight + heightOffset + viewportDist,
scrollHeight - (viewportTop + scrollTop),
scrollHeight - clientHeight
);
return clamp(-1 * top / dist);
}
export function scrollParents(element, overflowRe = /auto|scroll|hidden/, scrollable = false) {
const scrollEl = getScrollingElement(element);
let ancestors = parents(element).reverse();
ancestors = ancestors.slice(ancestors.indexOf(scrollEl) + 1);
const fixedIndex = findIndex(ancestors, el => css(el, 'position') === 'fixed');
if (~fixedIndex) {
ancestors = ancestors.slice(fixedIndex);
}
return [scrollEl].concat(ancestors.filter(parent =>
overflowRe.test(css(parent, 'overflow')) && (!scrollable || parent.scrollHeight > getViewportClientHeight(parent)))
).reverse();
}
export function getViewport(scrollElement) {
return scrollElement === getScrollingElement(scrollElement) ? window : scrollElement;
}
// iOS 12 returns <body> as scrollingElement
export function getViewportClientHeight(scrollElement) {
return (scrollElement === getScrollingElement(scrollElement) ? document.documentElement : scrollElement).clientHeight;
}
function getScrollingElement(element) {
const {document} = toWindow(element);
return document.scrollingElement || document.documentElement;
}