mami/src/mami.js/controls/views.js

209 lines
6.4 KiB
JavaScript

#include args.js
#include utility.js
const MamiViewsControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.check('body', undefined, value => value instanceof Element, true),
]);
const targetBody = options.body;
targetBody.classList.toggle('views', true);
const views = [];
const extractElement = elementInfo => {
let element;
if(elementInfo instanceof Element) {
element = elementInfo;
} else if('getElement' in elementInfo) {
element = elementInfo.getElement();
} else throw 'elementInfo is not a valid type';
if(!(element instanceof Element))
throw 'element is not an instance of Element';
if(element === targetBody)
throw 'element may not be the same as targetBody';
return element;
};
const updateZIncides = () => {
let index = 0;
for(const view of views)
extractElement(view).style.zIndex = (++index).toString();
};
const push = async (elementInfo, transition) => {
if(typeof elementInfo !== 'object')
throw 'elementInfo must be an object';
if(!views.includes(elementInfo))
views.push(elementInfo);
if(typeof elementInfo.onViewPush === 'function')
await elementInfo.onViewPush();
const element = extractElement(elementInfo);
element.classList.toggle('views-item', true);
element.classList.toggle('views-background', false);
element.classList.toggle('hidden', false);
if(!targetBody.contains(element))
targetBody.appendChild(element);
if(typeof elementInfo.onViewForeground === 'function')
await elementInfo.onViewForeground();
updateZIncides();
if(views.length > 1) {
const prevElemInfo = views[views.length - 2];
const prevElem = extractElement(prevElemInfo);
prevElem.classList.toggle('views-background', true);
if(typeof prevElemInfo.onViewBackground === 'function')
await prevElemInfo.onViewBackground();
if(transition !== false) {
if(typeof transition !== 'function' && typeof elementInfo.getViewTransition === 'function')
transition = elementInfo.getViewTransition('push');
if(typeof transition === 'function')
await transition({
toInfo: elementInfo,
toElem: element,
fromInfo: prevElemInfo,
fromElem: prevElem,
});
}
prevElem.classList.toggle('hidden', false);
}
};
const pop = async transition => {
const elementInfo = views.pop();
if(elementInfo === undefined)
throw 'views is empty';
const element = extractElement(elementInfo);
element.classList.toggle('views-background', true);
if(views.length > 0) {
const nextElemInfo = views[views.length - 1];
const nextElem = extractElement(nextElemInfo);
nextElem.classList.toggle('hidden', false);
nextElem.classList.toggle('views-background', false);
if(typeof nextElemInfo.onViewForeground === 'function')
await nextElemInfo.onViewForeground();
if(transition !== false) {
if(typeof transition !== 'function' && typeof elementInfo.getViewTransition === 'function')
transition = elementInfo.getViewTransition('pop');
if(typeof transition === 'function')
await transition({
toInfo: nextElemInfo,
toElem: nextElem,
fromInfo: elementInfo,
fromElem: element,
});
}
}
element.classList.toggle('hidden', true);
if(typeof elementInfo.onViewBackground === 'function')
await elementInfo.onViewBackground();
if(targetBody.contains(element))
targetBody.removeChild(element);
if(typeof elementInfo.onViewPop === 'function')
await elementInfo.onViewPop();
updateZIncides();
return elementInfo;
};
const current = () => {
if(views.length < 1)
return undefined;
return views[views.length - 1];
};
const raise = async (elementInfo, transition) => {
if(typeof elementInfo !== 'object')
throw 'elementInfo must be an object';
if(current() === elementInfo)
return;
const index = views.indexOf(elementInfo);
if(index >= 0)
views.splice(index, 1);
return await push(elementInfo, transition);
};
return {
push: push,
pop: pop,
raise: raise,
count: () => views.length,
current: current,
currentElement: () => {
const currentInfo = current();
if(currentInfo === undefined)
return undefined;
return extractElement(currentInfo);
},
includes: elementInfo => views.includes(elementInfo),
isCurrent: elementInfo => current() === elementInfo,
unshift: async elementInfo => {
if(views.length < 1)
return await push(elementInfo, null);
if(typeof elementInfo !== 'object')
throw 'elementInfo must be an object';
const element = extractElement(elementInfo);
if(!views.includes(elementInfo))
views.unshift(elementInfo);
element.classList.toggle('views-item', true);
element.classList.toggle('views-background', true);
element.classList.toggle('hidden', true);
if(!targetBody.contains(element))
targetBody.appendChild(element);
updateZIncides();
},
shift: async () => {
if(views.length < 2)
return await pop(null);
const elementInfo = views.shift();
const element = extractElement(elementInfo);
if(targetBody.contains(element))
targetBody.removeChild(element);
if(typeof elementInfo.onViewPop === 'function')
await elementInfo.onViewPop();
updateZIncides();
return elementInfo;
},
};
};