isomorphic-validation
Version:
Isomorphic javascript form validation library.
204 lines (168 loc) • 5.14 kB
JavaScript
import PositionObserver from '@itihon/position-observer';
import createApplyEffect from './create-apply-effect.js';
import retrieveIfHasOrCreate, { newMap } from '../utils/retrieve-if-has-or-create.js';
const positions = {
ABOVE: { translateY: () => `translateY(-100%)` },
LEVEL: { translateY: (offset) => `translateY(calc(${offset / 2}px - 50%))` },
BELOW: { translateY: (offset) => `translateY(${offset}px)` },
LEFT_BESIDE: { translateX: () => `translateX(-100%)` },
LEFT: {
translateX: (_, offsetHeight) =>
`translateX(calc(-100% + ${offsetHeight}px))`,
},
CENTER: {
translateX: (offsetWidth) => `translateX(calc(${offsetWidth / 2}px - 50%))`,
},
RIGHT: {
translateX: (offsetWidth, offsetHeight) =>
`translateX(${offsetWidth - offsetHeight}px)`,
},
RIGHT_BESIDE: { translateX: (offsetWidth) => `translateX(${offsetWidth}px)` },
};
const boxStyle = {
position: 'relative',
width: '',
height: '',
display: 'grid',
justifyContent: '',
alignItems: 'center',
};
const createBox = (content) => {
const box = document.createElement('div');
box.innerHTML = content;
return box;
};
const adjustContainer = (targetElement, targetRect, ctx) => {
if (!targetElement.offsetParent) {
ctx.containerStyle.display = 'none';
return;
}
const { left, top } = targetRect;
const {
startingPoint,
containerStyle,
position,
mode,
box,
stateValuesStyle,
} = ctx;
const { left: containerInitLeft, top: containerInitTop } =
startingPoint.getBoundingClientRect();
const translateX = left - containerInitLeft;
const translateY = top - containerInitTop;
const [VERT] = position.split('_', 1);
const [, ...HOR] = position.split('_', 3);
const offsetY = positions[VERT].translateY(targetElement.offsetHeight);
const offsetX = positions[HOR.join('_')].translateX(
targetElement.offsetWidth,
targetElement.offsetHeight,
);
const { offsetWidth, offsetHeight } = targetElement;
switch (mode) {
case 'MIN_SIDE':
boxStyle.width = `${offsetHeight}px`;
boxStyle.height = `${offsetHeight}px`;
boxStyle.justifyContent = 'center';
break;
case 'MAX_SIDE':
boxStyle.width = `${offsetWidth}px`;
boxStyle.height = ``;
boxStyle.justifyContent = '';
break;
}
Object.assign(box.style, boxStyle, stateValuesStyle);
containerStyle.transform = `${offsetX} ${offsetY} translate(${translateX}px, ${translateY}px)`;
containerStyle.display = 'block';
};
const createContainer = (
where,
id,
target,
position,
mode,
box,
stateValuesStyle,
) => {
const container = document.createElement('div');
const startingPoint = document.createElement('div');
const { style: containerStyle } = container;
const ctx = {
containerStyle,
startingPoint,
position,
mode,
box,
stateValuesStyle,
};
const observer = new PositionObserver(adjustContainer, ctx);
containerStyle.position = 'absolute';
containerStyle.left = '0';
containerStyle.top = '0';
startingPoint.style.position = 'absolute';
startingPoint.style.left = '0';
startingPoint.style.top = '0';
startingPoint.style.width = '0';
startingPoint.style.height = '0';
container.id = id;
startingPoint.dataset.for = 'apply-box-starting-point';
where.appendChild(container);
target.offsetParent.appendChild(startingPoint);
observer.observe(target);
return { container, observer, ctx };
};
const containerRegistry = new Map();
const boxesRegistry = new Map();
const setBoxEffect = (element, stateValues, validationResult, id) => {
// if (!element.offsetParent) {
// return;
// }
const { isValid } = validationResult;
const { parentNode } = element;
let box;
const binaryBox = retrieveIfHasOrCreate(boxesRegistry, stateValues, newMap);
const { value } = stateValues[isValid];
if (typeof value === 'function') {
box = retrieveIfHasOrCreate(binaryBox, isValid, createBox, '');
box.innerHTML = value(validationResult);
} else {
box = retrieveIfHasOrCreate(binaryBox, isValid, createBox, value);
}
const { container, observer, ctx } = retrieveIfHasOrCreate(
containerRegistry,
id,
createContainer,
parentNode,
id,
element,
stateValues.position,
stateValues.mode,
box,
stateValues.style,
);
ctx.position = stateValues.position;
ctx.mode = stateValues.mode;
ctx.box = box;
ctx.stateValuesStyle = stateValues.style;
// for (const target of observer.getTargets()) {
// if (target !== element) observer.unobserve(target);
// }
observer.disconnect();
observer.observe(element);
if (container.parentNode !== parentNode) {
parentNode.appendChild(container);
}
while (container.hasChildNodes()) {
container.lastElementChild.remove();
}
if (box.hasChildNodes()) {
container.style.display = 'block';
container.appendChild(box);
}
};
const applyBox = createApplyEffect(setBoxEffect, {
true: { delay: 0, value: '' },
false: { delay: 0, value: '' },
position: 'LEVEL_RIGHT_BESIDE',
mode: 'MIN_SIDE',
});
export { applyBox as default };