updates
This commit is contained in:
66
client/uikit/src/js/api/boot.js
Normal file
66
client/uikit/src/js/api/boot.js
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
101
client/uikit/src/js/api/component.js
Normal file
101
client/uikit/src/js/api/component.js
Normal 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;
|
||||
});
|
||||
78
client/uikit/src/js/api/global.js
Normal file
78
client/uikit/src/js/api/global.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
141
client/uikit/src/js/api/hooks.js
Normal file
141
client/uikit/src/js/api/hooks.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
24
client/uikit/src/js/api/index.js
Normal file
24
client/uikit/src/js/api/index.js
Normal 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;
|
||||
87
client/uikit/src/js/api/instance.js
Normal file
87
client/uikit/src/js/api/instance.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
323
client/uikit/src/js/api/state.js
Normal file
323
client/uikit/src/js/api/state.js
Normal 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;
|
||||
}
|
||||
}
|
||||
154
client/uikit/src/js/components/countdown.js
Normal file
154
client/uikit/src/js/components/countdown.js
Normal 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
|
||||
};
|
||||
}
|
||||
208
client/uikit/src/js/components/filter.js
Normal file
208
client/uikit/src/js/components/filter.js
Normal 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));
|
||||
}
|
||||
13
client/uikit/src/js/components/index.js
Normal file
13
client/uikit/src/js/components/index.js
Normal 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';
|
||||
@@ -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)}
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
160
client/uikit/src/js/components/internal/slider-transitioner.js
Normal file
160
client/uikit/src/js/components/internal/slider-transitioner.js
Normal 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));
|
||||
}
|
||||
118
client/uikit/src/js/components/internal/slideshow-animations.js
Normal file
118
client/uikit/src/js/components/internal/slideshow-animations.js
Normal 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}
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
349
client/uikit/src/js/components/lightbox-panel.js
Normal file
349
client/uikit/src/js/components/lightbox-panel.js
Normal 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;
|
||||
}
|
||||
104
client/uikit/src/js/components/lightbox.js
Normal file
104
client/uikit/src/js/components/lightbox.js
Normal 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;
|
||||
}
|
||||
129
client/uikit/src/js/components/notification.js
Normal file
129
client/uikit/src/js/components/notification.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
76
client/uikit/src/js/components/parallax.js
Normal file
76
client/uikit/src/js/components/parallax.js
Normal 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;
|
||||
}
|
||||
90
client/uikit/src/js/components/slider-parallax.js
Normal file
90
client/uikit/src/js/components/slider-parallax.js
Normal 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;
|
||||
}
|
||||
255
client/uikit/src/js/components/slider.js
Normal file
255
client/uikit/src/js/components/slider.js
Normal 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));
|
||||
}
|
||||
1
client/uikit/src/js/components/slideshow-parallax.js
Normal file
1
client/uikit/src/js/components/slideshow-parallax.js
Normal file
@@ -0,0 +1 @@
|
||||
export {default} from './slider-parallax';
|
||||
58
client/uikit/src/js/components/slideshow.js
Normal file
58
client/uikit/src/js/components/slideshow.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
424
client/uikit/src/js/components/sortable.js
Normal file
424
client/uikit/src/js/components/sortable.js
Normal 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];
|
||||
}
|
||||
136
client/uikit/src/js/components/tooltip.js
Normal file
136
client/uikit/src/js/components/tooltip.js
Normal 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');
|
||||
}
|
||||
}
|
||||
202
client/uikit/src/js/components/upload.js
Normal file
202
client/uikit/src/js/components/upload.js
Normal 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();
|
||||
}
|
||||
140
client/uikit/src/js/core/accordion.js
Normal file
140
client/uikit/src/js/core/accordion.js
Normal 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);
|
||||
}
|
||||
49
client/uikit/src/js/core/alert.js
Normal file
49
client/uikit/src/js/core/alert.js
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
92
client/uikit/src/js/core/core.js
Normal file
92
client/uikit/src/js/core/core.js
Normal 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';
|
||||
}
|
||||
57
client/uikit/src/js/core/cover.js
Normal file
57
client/uikit/src/js/core/cover.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
387
client/uikit/src/js/core/drop.js
Normal file
387
client/uikit/src/js/core/drop.js
Normal 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;
|
||||
}
|
||||
84
client/uikit/src/js/core/form-custom.js
Normal file
84
client/uikit/src/js/core/form-custom.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
};
|
||||
26
client/uikit/src/js/core/gif.js
Normal file
26
client/uikit/src/js/core/gif.js
Normal 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']
|
||||
}
|
||||
|
||||
};
|
||||
159
client/uikit/src/js/core/grid.js
Normal file
159
client/uikit/src/js/core/grid.js
Normal 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)
|
||||
);
|
||||
}
|
||||
91
client/uikit/src/js/core/height-match.js
Normal file
91
client/uikit/src/js/core/height-match.js
Normal 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;
|
||||
}
|
||||
92
client/uikit/src/js/core/height-viewport.js
Normal file
92
client/uikit/src/js/core/height-viewport.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
191
client/uikit/src/js/core/icon.js
Normal file
191
client/uikit/src/js/core/icon.js
Normal 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;
|
||||
}
|
||||
249
client/uikit/src/js/core/img.js
Normal file
249
client/uikit/src/js/core/img.js
Normal 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 = {};
|
||||
}
|
||||
41
client/uikit/src/js/core/index.js
Normal file
41
client/uikit/src/js/core/index.js
Normal 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';
|
||||
67
client/uikit/src/js/core/leader.js
Normal file
67
client/uikit/src/js/core/leader.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
129
client/uikit/src/js/core/margin.js
Normal file
129
client/uikit/src/js/core/margin.js
Normal 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
|
||||
};
|
||||
}
|
||||
145
client/uikit/src/js/core/modal.js
Normal file
145
client/uikit/src/js/core/modal.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
13
client/uikit/src/js/core/nav.js
Normal file
13
client/uikit/src/js/core/nav.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Accordion from './accordion';
|
||||
|
||||
export default {
|
||||
|
||||
extends: Accordion,
|
||||
|
||||
data: {
|
||||
targets: '> .uk-parent',
|
||||
toggle: '> a',
|
||||
content: '> ul'
|
||||
}
|
||||
|
||||
};
|
||||
413
client/uikit/src/js/core/navbar.js
Normal file
413
client/uikit/src/js/core/navbar.js
Normal 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
|
||||
};
|
||||
244
client/uikit/src/js/core/offcanvas.js
Normal file
244
client/uikit/src/js/core/offcanvas.js
Normal 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">');
|
||||
}
|
||||
61
client/uikit/src/js/core/overflow-auto.js
Normal file
61
client/uikit/src/js/core/overflow-auto.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
30
client/uikit/src/js/core/responsive.js
Normal file
30
client/uikit/src/js/core/responsive.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
43
client/uikit/src/js/core/scroll.js
Normal file
43
client/uikit/src/js/core/scroll.js
Normal 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)))}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
104
client/uikit/src/js/core/scrollspy-nav.js
Normal file
104
client/uikit/src/js/core/scrollspy-nav.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
};
|
||||
150
client/uikit/src/js/core/scrollspy.js
Normal file
150
client/uikit/src/js/core/scrollspy.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
335
client/uikit/src/js/core/sticky.js
Normal file
335
client/uikit/src/js/core/sticky.js
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
237
client/uikit/src/js/core/svg.js
Normal file
237
client/uikit/src/js/core/svg.js
Normal 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, '');
|
||||
}
|
||||
162
client/uikit/src/js/core/switcher.js
Normal file
162
client/uikit/src/js/core/switcher.js
Normal 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)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
33
client/uikit/src/js/core/tab.js
Normal file
33
client/uikit/src/js/core/tab.js
Normal 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});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
248
client/uikit/src/js/core/toggle.js
Normal file
248
client/uikit/src/js/core/toggle.js
Normal 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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
65
client/uikit/src/js/core/video.js
Normal file
65
client/uikit/src/js/core/video.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
38
client/uikit/src/js/mixin/animate.js
Normal file
38
client/uikit/src/js/mixin/animate.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
9
client/uikit/src/js/mixin/class.js
Normal file
9
client/uikit/src/js/mixin/class.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import {addClass, hasClass} from 'uikit-util';
|
||||
|
||||
export default {
|
||||
|
||||
connected() {
|
||||
!hasClass(this.$el, this.$name) && addClass(this.$el, this.$name);
|
||||
}
|
||||
|
||||
};
|
||||
21
client/uikit/src/js/mixin/container.js
Normal file
21
client/uikit/src/js/mixin/container.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
56
client/uikit/src/js/mixin/flex-bug.js
Normal file
56
client/uikit/src/js/mixin/flex-bug.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
} : {};
|
||||
97
client/uikit/src/js/mixin/internal/animate-fade.js
Normal file
97
client/uikit/src/js/mixin/internal/animate-fade.js
Normal 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')), []);
|
||||
}
|
||||
131
client/uikit/src/js/mixin/internal/animate-slide.js
Normal file
131
client/uikit/src/js/mixin/internal/animate-slide.js
Normal 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: ''};
|
||||
}
|
||||
40
client/uikit/src/js/mixin/internal/slideshow-animations.js
Normal file
40
client/uikit/src/js/mixin/internal/slideshow-animations.js
Normal 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)`;
|
||||
}
|
||||
75
client/uikit/src/js/mixin/internal/slideshow-transitioner.js
Normal file
75
client/uikit/src/js/mixin/internal/slideshow-transitioner.js
Normal 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));
|
||||
}
|
||||
36
client/uikit/src/js/mixin/media.js
Normal file
36
client/uikit/src/js/mixin/media.js
Normal 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;
|
||||
}
|
||||
248
client/uikit/src/js/mixin/modal.js
Normal file
248
client/uikit/src/js/mixin/modal.js
Normal 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);
|
||||
}
|
||||
333
client/uikit/src/js/mixin/parallax.js
Normal file
333
client/uikit/src/js/mixin/parallax.js
Normal 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;
|
||||
}
|
||||
75
client/uikit/src/js/mixin/position.js
Normal file
75
client/uikit/src/js/mixin/position.js
Normal 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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
77
client/uikit/src/js/mixin/slider-autoplay.js
Normal file
77
client/uikit/src/js/mixin/slider-autoplay.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
215
client/uikit/src/js/mixin/slider-drag.js
Normal file
215
client/uikit/src/js/mixin/slider-drag.js
Normal 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;
|
||||
}
|
||||
86
client/uikit/src/js/mixin/slider-nav.js
Normal file
86
client/uikit/src/js/mixin/slider-nav.js
Normal 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));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
23
client/uikit/src/js/mixin/slider-reactive.js
Normal file
23
client/uikit/src/js/mixin/slider-reactive.js
Normal 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']
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
232
client/uikit/src/js/mixin/slider.js
Normal file
232
client/uikit/src/js/mixin/slider.js
Normal 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)
|
||||
}
|
||||
53
client/uikit/src/js/mixin/slideshow.js
Normal file
53
client/uikit/src/js/mixin/slideshow.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
180
client/uikit/src/js/mixin/togglable.js
Normal file
180
client/uikit/src/js/mixin/togglable.js
Normal 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));
|
||||
};
|
||||
}
|
||||
17
client/uikit/src/js/uikit-core.js
Normal file
17
client/uikit/src/js/uikit-core.js
Normal 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;
|
||||
9
client/uikit/src/js/uikit.js
Normal file
9
client/uikit/src/js/uikit.js
Normal 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;
|
||||
89
client/uikit/src/js/util/ajax.js
Normal file
89
client/uikit/src/js/util/ajax.js
Normal 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;
|
||||
}
|
||||
115
client/uikit/src/js/util/animation.js
Normal file
115
client/uikit/src/js/util/animation.js
Normal 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');
|
||||
}
|
||||
|
||||
};
|
||||
51
client/uikit/src/js/util/attr.js
Normal file
51
client/uikit/src/js/util/attr.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
client/uikit/src/js/util/class.js
Normal file
93
client/uikit/src/js/util/class.js
Normal 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];
|
||||
}
|
||||
|
||||
};
|
||||
173
client/uikit/src/js/util/dimensions.js
Normal file
173
client/uikit/src/js/util/dimensions.js
Normal 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;
|
||||
}
|
||||
150
client/uikit/src/js/util/dom.js
Normal file
150
client/uikit/src/js/util/dom.js
Normal 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*</));
|
||||
}
|
||||
19
client/uikit/src/js/util/env.js
Normal file
19
client/uikit/src/js/util/env.js
Normal 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';
|
||||
141
client/uikit/src/js/util/event.js
Normal file
141
client/uikit/src/js/util/event.js
Normal 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};
|
||||
}
|
||||
75
client/uikit/src/js/util/fastdom.js
Normal file
75
client/uikit/src/js/util/fastdom.js
Normal 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);
|
||||
}
|
||||
109
client/uikit/src/js/util/filter.js
Normal file
109
client/uikit/src/js/util/filter.js
Normal 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);
|
||||
}
|
||||
19
client/uikit/src/js/util/index.js
Normal file
19
client/uikit/src/js/util/index.js
Normal 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';
|
||||
327
client/uikit/src/js/util/lang.js
Normal file
327
client/uikit/src/js/util/lang.js
Normal 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));
|
||||
}
|
||||
81
client/uikit/src/js/util/mouse.js
Normal file
81
client/uikit/src/js/util/mouse.js
Normal 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)};
|
||||
}
|
||||
155
client/uikit/src/js/util/options.js
Normal file
155
client/uikit/src/js/util/options.js
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
||||
107
client/uikit/src/js/util/player.js
Normal file
107
client/uikit/src/js/util/player.js
Normal 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));
|
||||
}
|
||||
148
client/uikit/src/js/util/position.js
Normal file
148
client/uikit/src/js/util/position.js
Normal 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
|
||||
};
|
||||
}
|
||||
191
client/uikit/src/js/util/promise.js
Normal file
191
client/uikit/src/js/util/promise.js
Normal 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);
|
||||
};
|
||||
118
client/uikit/src/js/util/selector.js
Normal file
118
client/uikit/src/js/util/selector.js
Normal 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) : '';
|
||||
}
|
||||
113
client/uikit/src/js/util/style.js
Normal file
113
client/uikit/src/js/util/style.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
client/uikit/src/js/util/viewport.js
Normal file
155
client/uikit/src/js/util/viewport.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user