import getXPath from 'get-xpath'; import PropTypes from 'prop-types'; import { mixpanelEvents, mixpanelService } from '@ea11y-apps/global/services'; import { isValidCSS, rgbOrRgbaToHex, } from '@ea11y-apps/global/utils/color-contrast-helpers'; import { APIScanner } from '@ea11y-apps/scanner/api/APIScanner'; import { BACKGROUND_ELEMENT_CLASS, BLOCKS, } from '@ea11y-apps/scanner/constants'; import { useScannerWizardContext } from '@ea11y-apps/scanner/context/scanner-wizard-context'; import { scannerItem } from '@ea11y-apps/scanner/types/scanner-item'; import { buildPathToParent } from '@ea11y-apps/scanner/utils/build-path-to-parent'; import { focusOnElement, removeExistingFocus, } from '@ea11y-apps/scanner/utils/focus-on-element'; import { getElementByXPath } from '@ea11y-apps/scanner/utils/get-element-by-xpath'; import { getElementCSSSelector } from '@ea11y-apps/scanner/utils/get-element-css-selector'; import { getOuterHtmlByXpath } from '@ea11y-apps/scanner/utils/get-outer-html-by-xpath'; import { useEffect, useRef, useState } from '@wordpress/element'; export const useColorContrastForm = ({ item, current, setCurrent }) => { const { colorContrastData, resolved: resolvedBlock, setResolved, isResolved, isManage, setOpenedBlock, setColorContrastData, setIsManageChanged, updateRemediationList, currentScanId, } = useScannerWizardContext(); const type = isManage ? 'manage' : 'main'; const [loading, setLoading] = useState(false); const [firstOpen, setFirstOpen] = useState(true); const [parentChanged, setParentChanged] = useState(false); const isGlobal = colorContrastData?.[type]?.[current]?.isGlobal || item.global || false; const isGlobalRef = useRef(null); useEffect(() => { if (item?.node) { isGlobalRef.current = isGlobal; } }, [current]); const setIsGlobal = (value) => { updateData({ isGlobal: value, }); }; const updateData = (data) => { const updData = [...colorContrastData?.[type]]; updData[current] = { ...(colorContrastData?.[type]?.[current] || {}), ...data, }; setColorContrastData({ ...colorContrastData, [type]: updData, }); if (data.color || data.background) { const rule = buildCSSRule(updData[current]); updateCSS(rule); } }; const sendEvent = (method) => { mixpanelService.sendEvent(mixpanelEvents.backgroundAdaptorChanged, { method, }); }; useEffect(() => { if (!isManage && !firstOpen && isResolved(BLOCKS.colorContrast)) { removeExistingFocus(); setOpenedBlock(BLOCKS.main); } setFirstOpen(false); }, [isResolved(BLOCKS.colorContrast)]); const { color = item.messageArgs[3] || rgbOrRgbaToHex( window.getComputedStyle(item.node).getPropertyValue('color'), ), background = item.messageArgs[4], parents = item.isEdit ? buildPathToParent(item.node, item.parentNode) : [item.path.dom], resolved = false, backgroundChanged = item.isEdit, } = colorContrastData[type]?.[current] || {}; useEffect(() => { if (isManage && parentChanged) { const styles = document.getElementById('ea11y-remediation-styles'); if (styles) { styles.innerHTML = styles.innerHTML.replace(item.data.rule, ''); } } }, [parentChanged]); const changeColor = (updColor) => { updateData({ color: updColor, resolved: false }); }; const changeBackground = (updBackground) => { const element = getElementByXPath(parents.at(-1)); if (!element) { return; } updateData({ background: updBackground, resolved: false, backgroundChanged: true, }); }; const setParentLarger = () => { const element = getElementByXPath(parents.at(-1)); const parent = element?.parentElement; if (!element || !parent) { return; } try { const xpath = getXPath(parent, { ignoreId: true }); focusOnElement(parent, BACKGROUND_ELEMENT_CLASS); updateData({ parents: [...parents, xpath], resolved: false, }); setParentChanged(true); sendEvent('plus'); } catch (error) { console.warn('Failed to get XPath for parent element:', error); } }; const setParentSmaller = () => { const newParents = parents.slice(0, -1); const nextElement = getElementByXPath(newParents.at(-1)); if (parents.length <= 1 || !nextElement) { return; } if (newParents.length > 1) { focusOnElement(nextElement, BACKGROUND_ELEMENT_CLASS); } else { removeExistingFocus(BACKGROUND_ELEMENT_CLASS); } updateData({ parents: newParents, resolved: false }); setParentChanged(true); sendEvent('minus'); }; const isValidHexColor = (str) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(str.trim()); const buildCSSRule = (data) => { const currentParents = data.parents || parents; const currentParent = currentParents.length > 0 ? currentParents.at(-1) : item.path.dom; if ( (data.color && !isValidHexColor(data.color)) || (data.background && !isValidHexColor(data.background)) ) { throw new Error('Invalid hex color input detected'); } try { const bgElement = getElementByXPath(currentParent); const colorSelector = getElementCSSSelector(item.node); const bgSelector = getElementCSSSelector(bgElement); const colorRule = data.color ? `${colorSelector} {color: ${data.color} !important;}` : ''; const bgRule = data.background ? `${bgSelector} {background-color: ${data.background} !important;}` : ''; const css = `${colorRule}${bgRule}`; return isValidCSS(css) ? css : ''; } catch (e) { console.warn('Failed to convert XPath to CSS selector', e); return ''; } }; const updateCSS = (rule) => { let styles = document.getElementById('ea11y-remediation-styles-edit'); if (!styles) { styles = document.createElement('style'); styles.id = 'ea11y-remediation-styles-edit'; document.body.appendChild(styles); } styles.innerHTML = rule; }; const onUpdate = async () => { const rule = buildCSSRule(colorContrastData?.[type]?.[current]); const find = getOuterHtmlByXpath(item.path.dom); const parentXPath = parents.length > 0 ? parents.at(-1) : null; const parentFind = getOuterHtmlByXpath(parentXPath); try { setLoading(true); const remediation = colorContrastData?.[type]?.[current]?.remediation; const id = item.id || remediation.id; const data = item.data || JSON.parse(remediation.content); const updContent = JSON.stringify({ ...data, rule, find, parentFind, parentXPath, }); await APIScanner.updateRemediationContent({ url: window?.ea11yScannerData?.pageData?.url, id, content: updContent, global: isGlobal, }); setIsManageChanged(true); removeExistingFocus(); setParentChanged(false); updateCSS(rule); isGlobalRef.current = isGlobal; void updateRemediationList(); } catch (e) { console.error(e); } finally { setLoading(false); } }; const onSubmit = async () => { const parentXPath = parents.length > 0 ? parents.at(-1) : null; const parentFind = getOuterHtmlByXpath(parentXPath); setLoading(true); try { const rule = buildCSSRule(colorContrastData?.[type]?.[current]); const response = await APIScanner.submitRemediation({ url: window?.ea11yScannerData?.pageData?.url, remediation: { rule, category: item.reasonCategory.match(/\((AAA?|AA?|A)\)/)?.[1] || '', type: 'STYLES', xpath: item.path.dom, find: item.snippet, parentFind, parentXPath, }, global: isGlobal, rule: item.ruleId, group: BLOCKS.colorContrast, }); await APIScanner.resolveIssue(currentScanId); updateData({ remediation: response.remediation, resolved: true }); removeExistingFocus(); setCurrent(current + 1); setResolved(resolvedBlock + 1); setParentChanged(false); updateCSS(rule); isGlobalRef.current = isGlobal; void updateRemediationList(); } catch (error) { console.error('Failed to submit remediation:', error); } finally { setLoading(false); } }; const isDisabled = resolved && colorContrastData?.[type]?.[current]?.isGlobal === isGlobalRef.current; return { isGlobal, setIsGlobal, color, background, parents, isDisabled, resolved, backgroundChanged, parentChanged, loading, changeColor, changeBackground, setParentLarger, setParentSmaller, onSubmit, onUpdate, }; }; useColorContrastForm.propTypes = { item: scannerItem.isRequired, current: PropTypes.number.isRequired, setCurrent: PropTypes.func.isRequired, }; Cửa hàng – EM SANG Boutique – Thương hiệu thời trang nữ phong cách đa dạng với hàng nghìn mẫu thiết kế https://emsang.vn Thương hiệu thời trang nữ phong cách đa dạng với hàng nghìn mẫu thiết kế Wed, 25 Jun 2025 16:21:11 +0000 vi hourly 1 https://wordpress.org/?v=7.0 https://emsang.vn/wp-content/uploads/2024/07/cropped-favicon-32x32.png Cửa hàng – EM SANG Boutique – Thương hiệu thời trang nữ phong cách đa dạng với hàng nghìn mẫu thiết kế https://emsang.vn 32 32 Áo thun Zipper và quần Kaki – Hot Item https://emsang.vn/san-pham/ao-thun-zipper-va-quan-kaki-hot-item/ https://emsang.vn/san-pham/ao-thun-zipper-va-quan-kaki-hot-item/#respond Wed, 25 Jun 2025 16:21:11 +0000 https://emsang.vn/?post_type=product&p=3814 Sự kết hợp của 2 items tối giản, áo thun gân mát mịn và quần kaki form đứng che khuyết điểm chân, tạo nên diện mạo thanh lịch nhưng thoải mái trong mọi khoảnh khắc.
Chất liệu: Thun cotton 2 chiều
Màu sắc: Nâu
Size: S, M, L
]]>
https://emsang.vn/san-pham/ao-thun-zipper-va-quan-kaki-hot-item/feed/ 0
Đầm Lụa 11011700 https://emsang.vn/san-pham/dam-lua-11011700/ https://emsang.vn/san-pham/dam-lua-11011700/#respond Wed, 26 Mar 2025 17:11:51 +0000 https://emsang.vn/?post_type=product&p=3790 https://emsang.vn/san-pham/dam-lua-11011700/feed/ 0 Váy Lụa Dài 17011694 https://emsang.vn/san-pham/vay-lua-dai-17011694/ https://emsang.vn/san-pham/vay-lua-dai-17011694/#respond Wed, 26 Mar 2025 17:03:44 +0000 https://emsang.vn/?post_type=product&p=3781 https://emsang.vn/san-pham/vay-lua-dai-17011694/feed/ 0 Sơ Mi Kiểu Dài Tay 10611697 https://emsang.vn/san-pham/so-mi-kieu-dai-tay-10611697/ https://emsang.vn/san-pham/so-mi-kieu-dai-tay-10611697/#respond Wed, 26 Mar 2025 16:50:42 +0000 https://emsang.vn/?post_type=product&p=3772 https://emsang.vn/san-pham/so-mi-kieu-dai-tay-10611697/feed/ 0 Áo Sơ Mi Dài Tay 10611698 https://emsang.vn/san-pham/ao-so-mi-dai-tay-10611698/ https://emsang.vn/san-pham/ao-so-mi-dai-tay-10611698/#respond Wed, 26 Mar 2025 16:41:37 +0000 https://emsang.vn/?post_type=product&p=3764 https://emsang.vn/san-pham/ao-so-mi-dai-tay-10611698/feed/ 0 Áo Khoác Vest 11511509 https://emsang.vn/san-pham/ao-khoac-vest-11511509/ https://emsang.vn/san-pham/ao-khoac-vest-11511509/#respond Wed, 26 Mar 2025 16:36:11 +0000 https://emsang.vn/?post_type=product&p=3755 https://emsang.vn/san-pham/ao-khoac-vest-11511509/feed/ 0 Quần Short Jeans 15011675 https://emsang.vn/san-pham/quan-short-jeans-15011675/ https://emsang.vn/san-pham/quan-short-jeans-15011675/#respond Wed, 26 Mar 2025 16:27:28 +0000 https://emsang.vn/?post_type=product&p=3743 https://emsang.vn/san-pham/quan-short-jeans-15011675/feed/ 0 Váy Kiểu Dài 17010852 https://emsang.vn/san-pham/vay-kieu-dai-17010852/ https://emsang.vn/san-pham/vay-kieu-dai-17010852/#respond Wed, 26 Mar 2025 16:18:44 +0000 https://emsang.vn/?post_type=product&p=3735 https://emsang.vn/san-pham/vay-kieu-dai-17010852/feed/ 0 Jean Ống Rộng 10611656 https://emsang.vn/san-pham/jean-ong-rong-10611656/ https://emsang.vn/san-pham/jean-ong-rong-10611656/#respond Wed, 26 Mar 2025 16:12:20 +0000 https://emsang.vn/?post_type=product&p=3726 https://emsang.vn/san-pham/jean-ong-rong-10611656/feed/ 0 Áo Sơ Mi Kiểu 10611656 https://emsang.vn/san-pham/ao-so-mi-kieu-10611656/ https://emsang.vn/san-pham/ao-so-mi-kieu-10611656/#respond Wed, 26 Mar 2025 16:04:17 +0000 https://emsang.vn/?post_type=product&p=3713 https://emsang.vn/san-pham/ao-so-mi-kieu-10611656/feed/ 0