- 更新 CI/CD 子域名反向代理配置 - 调整 Nginx 静态文件服务器配置 - 更新 CONTEXT.md 领域共享语言文档 - 添加 CLAUDE.md 代理工作指南 - 更新 Playwright E2E 测试配置
147 lines
4.2 KiB
JavaScript
147 lines
4.2 KiB
JavaScript
/**
|
|
* Browser-side DOM helpers for Impeccable live mode.
|
|
*
|
|
* Kept separate from live-browser.js so future browser script parts can share
|
|
* chrome mounting, lookup, focus, and picker helpers without depending on the
|
|
* full overlay UI bundle.
|
|
*/
|
|
(function (root) {
|
|
'use strict';
|
|
if (!root) return;
|
|
|
|
function createLiveBrowserDomHelpers({
|
|
prefix,
|
|
skipTags,
|
|
document: doc = root.document,
|
|
css = root.CSS,
|
|
crypto = root.crypto,
|
|
} = {}) {
|
|
if (!prefix) throw new Error('prefix required');
|
|
if (!doc) throw new Error('document required');
|
|
const tagsToSkip = skipTags || new Set();
|
|
|
|
function own(el) {
|
|
return el && (el.id?.startsWith(prefix) || el.closest?.('[id^="' + prefix + '"]'));
|
|
}
|
|
|
|
function pickable(el) {
|
|
if (!el || el.nodeType !== 1) return false;
|
|
if (tagsToSkip.has(String(el.tagName || '').toLowerCase())) return false;
|
|
if (own(el)) return false;
|
|
const r = el.getBoundingClientRect();
|
|
return r.width >= 20 && r.height >= 20;
|
|
}
|
|
|
|
function desc(el) {
|
|
if (!el) return '';
|
|
let s = el.tagName.toLowerCase();
|
|
if (el.id) s += '#' + el.id;
|
|
else if (el.classList.length) s += '.' + [...el.classList].slice(0, 2).join('.');
|
|
return s;
|
|
}
|
|
|
|
function rectIsUsableAnchor(rect) {
|
|
return !!rect && rect.width > 0.5 && rect.height > 0.5;
|
|
}
|
|
|
|
function makeFrozenAnchor(el) {
|
|
if (!el || !el.getBoundingClientRect) return null;
|
|
const r = el.getBoundingClientRect();
|
|
if (!rectIsUsableAnchor(r)) return null;
|
|
const rect = {
|
|
x: r.x, y: r.y,
|
|
top: r.top, left: r.left,
|
|
right: r.right, bottom: r.bottom,
|
|
width: r.width, height: r.height,
|
|
};
|
|
return {
|
|
__impeccableFrozenAnchor: true,
|
|
tagName: el.tagName || 'DIV',
|
|
id: el.id || '',
|
|
classList: el.classList ? [...el.classList] : [],
|
|
hasAttribute: () => false,
|
|
getBoundingClientRect: () => rect,
|
|
};
|
|
}
|
|
|
|
function id8() {
|
|
if (crypto?.randomUUID) return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
|
|
return (Math.random().toString(16).slice(2) + Date.now().toString(16)).slice(0, 8);
|
|
}
|
|
|
|
function cssId(id) {
|
|
if (css?.escape) return css.escape(id);
|
|
return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
|
|
}
|
|
|
|
function liveUiRoot() {
|
|
const uiRoot = root.__IMPECCABLE_LIVE_UI_ROOT__;
|
|
if (uiRoot && typeof uiRoot.appendChild === 'function') return uiRoot;
|
|
return doc.body;
|
|
}
|
|
|
|
function uiAppend(el) {
|
|
liveUiRoot().appendChild(el);
|
|
return el;
|
|
}
|
|
|
|
function uiAppendStyle(styleEl) {
|
|
const uiRoot = liveUiRoot();
|
|
if (uiRoot && uiRoot !== doc.body) uiRoot.appendChild(styleEl);
|
|
else doc.head.appendChild(styleEl);
|
|
return styleEl;
|
|
}
|
|
|
|
function uiGetById(id) {
|
|
const uiRoot = liveUiRoot();
|
|
if (uiRoot?.getElementById) {
|
|
const found = uiRoot.getElementById(id);
|
|
if (found) return found;
|
|
}
|
|
if (uiRoot?.querySelector) {
|
|
const found = uiRoot.querySelector('#' + cssId(id));
|
|
if (found) return found;
|
|
}
|
|
return doc.getElementById(id);
|
|
}
|
|
|
|
function activeElementDeep() {
|
|
let active = doc.activeElement;
|
|
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
return active;
|
|
}
|
|
|
|
function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
|
|
if (!rootEl) return;
|
|
if (setPointerEvents) {
|
|
rootEl.style.setProperty('pointer-events', 'auto', 'important');
|
|
}
|
|
const stop = (e) => e.stopPropagation();
|
|
rootEl.addEventListener('pointerdown', stop);
|
|
rootEl.addEventListener('mousedown', stop);
|
|
rootEl.addEventListener('focusin', stop);
|
|
}
|
|
|
|
return {
|
|
own,
|
|
pickable,
|
|
desc,
|
|
rectIsUsableAnchor,
|
|
makeFrozenAnchor,
|
|
id8,
|
|
cssId,
|
|
liveUiRoot,
|
|
uiAppend,
|
|
uiAppendStyle,
|
|
uiGetById,
|
|
activeElementDeep,
|
|
defangOutsideHandlers,
|
|
};
|
|
}
|
|
|
|
root.__IMPECCABLE_LIVE_DOM__ = {
|
|
version: 1,
|
|
createLiveBrowserDomHelpers,
|
|
};
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|