UNPKG

@public-ui/components

Version:

Contains all web components that belong to KoliBri - The accessible HTML-Standard.

376 lines (375 loc) 15.9 kB
/*! * KoliBri - The accessible HTML-Standard */ import { getCssStyle, patchTheme, patchThemeTag } from "adopted-style-sheets"; import rgba from "rgba-convert"; import { hex, score } from "wcag-contrast"; import { getDocument, getExperimentalMode, Log } from "./dev.utils"; import { devHint } from "./a11y.tipps"; const OBJECT_OBJECT = /\[object Object\]/; export const objectObjectHandler = (value, cb) => { if (typeof value === 'string' && OBJECT_OBJECT.test(value)) { return; } cb(); }; export const emptyStringByArrayHandler = (value, cb) => { if (typeof value === 'string' && value === '') { return; } cb(); }; export const setEventTarget = (event, target) => { if (getExperimentalMode()) { Log.debug([event, target]); Log.debug(`↑ We propagate the (submit) event to this target.`); } Object.defineProperty(event, 'target', { value: target, writable: false, }); }; const patchState = (component) => { var _a, _b, _c; (_a = component.nextHooks) === null || _a === void 0 ? void 0 : _a.forEach((hooks, key) => { var _a; const beforePatch = hooks.get('beforePatch'); if (typeof beforePatch === 'function') { beforePatch((_a = component.nextState) === null || _a === void 0 ? void 0 : _a.get(key), component.nextState, component, key); } }); if (((_b = component.nextState) === null || _b === void 0 ? void 0 : _b.size) > 0) { component.state = Object.assign(Object.assign({}, component.state), Object.fromEntries(component.nextState)); delete component.nextState; (_c = component.nextHooks) === null || _c === void 0 ? void 0 : _c.forEach((hooks, key) => { const afterPatch = hooks.get('afterPatch'); if (typeof afterPatch === 'function') { afterPatch(component.state[key], component.state, component, key); } }); } delete component.nextHooks; }; export const setState = (component, propName, value, hooks = {}) => { var _a, _b; if (component.nextHooks === undefined) { component.nextHooks = new Map(); } if (component.nextState === undefined) { component.nextState = new Map(); } const nextHooks = component.nextHooks.get(propName); if (nextHooks instanceof Map === false) { component.nextHooks.set(propName, new Map()); } if (typeof hooks.afterPatch === 'function') { (_a = component.nextHooks.get(propName)) === null || _a === void 0 ? void 0 : _a.set('afterPatch', hooks.afterPatch); } if (typeof hooks.beforePatch === 'function') { (_b = component.nextHooks.get(propName)) === null || _b === void 0 ? void 0 : _b.set('beforePatch', hooks.beforePatch); } component.nextState.set(propName, value); patchState(component); }; const logWarn = (component, propName, value, requiredGeneric) => { devHint(`[${component.constructor.name}] The property value: (${value}) for '${propName}' is not valid. Allowed values are: ${Array.from(requiredGeneric).join(', ')}`); }; export function watchValidator(component, propName, validationFunction, requiredGeneric, value, options = {}) { if (validationFunction(value)) { setState(component, propName, value, options.hooks); } else if (value === undefined && options.required !== true && validationFunction(options.defaultValue)) { setState(component, propName, options.defaultValue, options.hooks); } else { if (!options.required) { requiredGeneric.add(null); } logWarn(component, propName, value, requiredGeneric); } } export const watchBoolean = (component, propName, value, options) => { watchValidator(component, propName, (value) => typeof value === 'boolean', new Set(['Boolean {true, false}']), value, options); }; export const watchString = (component, propName, value, options = {}) => { const minLength = typeof options.minLength === 'number' ? options === null || options === void 0 ? void 0 : options.minLength : 0; watchValidator(component, propName, (value) => typeof value === 'string' && value.length >= minLength && (typeof (options === null || options === void 0 ? void 0 : options.maxLength) === 'undefined' || value.length <= options.maxLength), new Set([`String`]), value, options); }; export const watchNumber = (component, propName, value, options) => { watchValidator(component, propName, (value) => typeof value === 'number' && (typeof (options === null || options === void 0 ? void 0 : options.min) === 'undefined' || (typeof (options === null || options === void 0 ? void 0 : options.min) === 'number' && value >= options.min)) && (typeof (options === null || options === void 0 ? void 0 : options.max) === 'undefined' || (typeof (options === null || options === void 0 ? void 0 : options.max) === 'number' && value <= options.max)), new Set(['Number']), value, options); }; export const watchJsonArrayString = (component, propName, itemValidation, value, arrayValidation = (items) => items === items, options = {}) => { emptyStringByArrayHandler(value, () => { objectObjectHandler(value, () => { if (typeof value === 'undefined') { value = []; } try { try { value = parseJson(value); } catch (_a) { } if (Array.isArray(value)) { const invalid = value.find((item) => !itemValidation(item)); if (invalid === undefined && arrayValidation(value)) { setState(component, propName, value, options.hooks); } else { objectObjectHandler(invalid, () => { Log.debug(invalid); throw new Error(`↑ The schema for the property (_options) is not valid. The value will not be changed.`); }); } } else { objectObjectHandler(value, () => { Log.debug(value); throw new Error(`↑ The schema for the property (_options) is not valid. The value will not be changed.`); }); } } catch (error) { Log.debug(error); } }); }); }; export const stringifyJson = (value) => { try { return JSON.stringify(value).replace(/"/g, "'"); } catch (_a) { Log.warn(['stringifyJson', value]); Log.error(`↑ The JSON could not be converted to a string. A stringifiable JSON is expected.`); throw new Error(); } }; const JSON_CHARS = /^[{[]/; export const parseJson = (value) => { if (typeof value === 'string') { try { return JSON.parse(value); } catch (_a) { if (JSON_CHARS.test(value)) { try { return JSON.parse(value.replace(/'/g, '"')); } catch (_b) { Log.warn(['parseJson', value]); Log.error(`↑ The JSON string could not be parsed. Make sure that single quotes in the text are escaped (&#8216;).`); } } } } throw new Error(); }; export const mapBoolean2String = (value) => { return typeof value === 'boolean' ? (value === true ? 'true' : 'false') : undefined; }; export const mapStringOrBoolean2String = (value) => { return typeof value === 'string' ? value : mapBoolean2String(value); }; const querySelectorShadow = (selector, rootNode) => { const visited = new Set(); const queue = [rootNode]; let index = 0; while (index < queue.length) { const current = queue[index++]; if (visited.has(current)) { continue; } visited.add(current); if (current instanceof Element && current.matches(selector)) { return current; } const children = Array.from(current.children || []); for (let i = 0; i < children.length; i++) { queue.push(children[i]); } if (current instanceof HTMLElement && current.shadowRoot) { queue.push(current.shadowRoot); } if (current instanceof HTMLSlotElement) { const assigned = current.assignedNodes({ flatten: true }); for (let i = 0; i < assigned.length; i++) { if (assigned[i] instanceof Element) { queue.push(assigned[i]); } } } } return null; }; const querySelectorAllShadow = (selector, rootNode) => { const visited = new Set(); const results = []; const resultSet = new Set(); const queue = [rootNode]; let index = 0; while (index < queue.length) { const current = queue[index++]; if (visited.has(current)) { continue; } visited.add(current); if (current instanceof Element && current.matches(selector) && !resultSet.has(current)) { results.push(current); resultSet.add(current); } const children = Array.from(current.children || []); for (let i = 0; i < children.length; i++) { queue.push(children[i]); } if (current instanceof HTMLElement && current.shadowRoot) { queue.push(current.shadowRoot); } if (current instanceof HTMLSlotElement) { const assigned = current.assignedNodes({ flatten: true }); for (let i = 0; i < assigned.length; i++) { if (assigned[i] instanceof Element) { queue.push(assigned[i]); } } } } return results; }; export const koliBriQuerySelector = (selector, node) => querySelectorShadow(selector, node || getDocument()); export const koliBriQuerySelectorAll = (selector, node) => querySelectorAllShadow(selector, node || getDocument()); let DEFAULT_COLOR_CONTRAST = null; const getDefaultColorContrast = () => { DEFAULT_COLOR_CONTRAST = DEFAULT_COLOR_CONTRAST || { backgroundColor: '#00000000', color: '#00000000', domNode: getDocument().body, level: 'Fail', score: 1, }; return DEFAULT_COLOR_CONTRAST; }; const TRANSPARENT_REGEXP = /(\d+, ){3}0\)/; export const koliBriA11yColorContrast = (domNode, a11yColorContrast = getDefaultColorContrast()) => { const computedStyle = getComputedStyle(domNode); const hexBG = TRANSPARENT_REGEXP.test(computedStyle.backgroundColor) ? a11yColorContrast.backgroundColor : rgba.hex(computedStyle.backgroundColor); const hexC = TRANSPARENT_REGEXP.test(computedStyle.color) ? a11yColorContrast.color : rgba.hex(computedStyle.color); const diff = hex(hexBG, hexC); const contrast = { backgroundColor: hexBG, color: hexC, domNode: domNode, level: score(diff), score: diff, }; if (diff < 4.5) { Log.error([ 'Color-Contrast-Error', { backgroundColor: contrast.backgroundColor, color: contrast.color, level: contrast.level, score: contrast.score, }, contrast.domNode, ]); } return contrast; }; export const koliBriQuerySelectorColors = (selector, a11yColorContrast = getDefaultColorContrast()) => { if (a11yColorContrast.domNode instanceof HTMLElement) { a11yColorContrast = koliBriA11yColorContrast(a11yColorContrast.domNode, a11yColorContrast); } const selectedNode = a11yColorContrast.domNode.querySelector(selector); if (selectedNode === null) { const nodeList = a11yColorContrast.domNode.querySelectorAll('[class="hydrated"]'); for (let i = 0; i < nodeList.length; i++) { a11yColorContrast.domNode = nodeList[i]; a11yColorContrast = koliBriQuerySelectorColors(selector, a11yColorContrast); if (a11yColorContrast.domNode !== null) { break; } } return a11yColorContrast; } else { return koliBriA11yColorContrast(selectedNode, a11yColorContrast); } }; export class KoliBriUtils { static queryHtmlElementColors(targetNode, a11yColorContrast, recursion = false, log = true) { let returnValue = null; if (recursion === true || KoliBriUtils.executionLock === false) { if (recursion === false) { KoliBriUtils.cache.clear(); KoliBriUtils.cache.set(a11yColorContrast.domNode, a11yColorContrast); KoliBriUtils.executionLock = true; if (log === true) { Log.debug(`[KoliBriUtils] Color contrast analysis started...`); } } if (targetNode === a11yColorContrast.domNode) { returnValue = a11yColorContrast; } else { const children = new Set(); if (a11yColorContrast.domNode.shadowRoot) { const shadowChildren = a11yColorContrast.domNode.shadowRoot.children; for (let i = 0; i < shadowChildren.length; i++) { children.add(shadowChildren[i]); } } const slotElement = a11yColorContrast.domNode; if (typeof slotElement.assignedNodes === 'function') { const slotChildren = slotElement.assignedNodes(); for (let i = 0; i < slotChildren.length; i++) { if (slotChildren[i] instanceof HTMLElement) { children.add(slotChildren[i]); } } } const domChildren = a11yColorContrast.domNode.children; for (let i = 0; i < domChildren.length; i++) { children.add(domChildren[i]); } const arrayChildren = Array.from(children); for (let i = 0; i < arrayChildren.length; i++) { let colorContrast = KoliBriUtils.cache.get(arrayChildren[i]); if (colorContrast === undefined) { colorContrast = koliBriA11yColorContrast(arrayChildren[i], a11yColorContrast); } KoliBriUtils.cache.set(arrayChildren[i], colorContrast); const colors = KoliBriUtils.queryHtmlElementColors(targetNode, colorContrast, true, false); if (colors !== null) { returnValue = colors; break; } } } } else { Log.debug(`[KoliBriUtils] Call aborted because a color contrast analysis is currently being executed.`); } if (recursion === false) { if (log === true) { Log.debug(`[KoliBriUtils] Color contrast analysis finished (${KoliBriUtils.cache.size} DOM elements are analysed).`); } KoliBriUtils.executionLock = false; KoliBriUtils.cache.clear(); } return returnValue; } } KoliBriUtils.executionLock = false; KoliBriUtils.cache = new Map(); export class KoliBriDevHelper { } KoliBriDevHelper.getCssStyle = getCssStyle; KoliBriDevHelper.patchTheme = patchTheme; KoliBriDevHelper.patchThemeTag = patchThemeTag; KoliBriDevHelper.querySelector = koliBriQuerySelector; KoliBriDevHelper.querySelectorAll = koliBriQuerySelectorAll; KoliBriDevHelper.stringifyJson = stringifyJson; //# sourceMappingURL=prop.validators.js.map