/** * scripts/v11b-patch.mjs —— v11b 验收用渲染路径补丁(见 docs/v11b-report.md 的「未达成 / 取舍」一节)。 * * 上一轮的写法是「每个视图 updated() 里调 cjk(this.renderRoot)」,两个问题: * 1. Lit 每次渲染都会把模板里的字符串按原样重写进文本节点 —— 渲染后改的 U+2009 活不过下一次渲染; * 2. 33 个自定义元素里只有 7 个调了 —— 卡片 / 徽标 / 顶栏 / 五阶段页里的数字全漏掉。 * 这个文件把同一套规则挂到 **MutationObserver** 上:文本一变就当场过一遍 mix()(幂等)。 * 规则与 src/utils/type.js 的 spaceCJK 一致: * 汉字 ↔ 拉丁/数字交界两侧 U+2009;交界处的普通空格 / 全角空格换成 U+2009;连续数字整体算一个单位; * /
///.kf-mono、URL、文件名跳过;textarea / input 值、aria-label 等属性不动。
 */
(function installKfCjk() {
  const THIN = ' ';
  const EXEMPT = new Set(['CODE', 'PRE', 'KBD', 'SAMP', 'SCRIPT', 'STYLE', 'TEXTAREA', 'NOSCRIPT', 'TEMPLATE']);
  const URLISH = /https?:\/\/|www\.|[\w-]+\.(?:js|mjs|json|md|css|png|jpg|svg|html|pdf)\b/;
  const MIX = new RegExp(
    '([\\u4e00-\\u9fa5,\\u3001\\u3002])[ \\t\\u00a0\\u3000]*(?=[A-Za-z0-9])' + // 汉字(含 ,、。)+ 空白 + 拉丁
      '|(?<=[A-Za-z0-9,.%])[ \\t\\u00a0\\u3000]*([\\u4e00-\\u9fa5])', // 拉丁 + 空白 + 汉字
    'g',
  );
  const mix = (s) => String(s).replace(MIX, (m, a, b) => (b !== undefined ? THIN + b : a + THIN));
  const hasThin = (s) => s.split('').some((c) => c.charCodeAt(0) === 0x2009);
  const NEAR = /[\u4e00-\u9fa5][ \t\u00a0\u3000]*[A-Za-z0-9]|[A-Za-z0-9][ \t\u00a0\u3000]*[\u4e00-\u9fa5]/;
  const near = (s) => NEAR.test(s);
  /* 需要处理:汉字与拉丁之间是普通空格(NEAR),或者已经夹着一个 U+2009(幂等归一) */
  const hasNear = (s) => NEAR.test(s) || /[\u4e00-\u9fa5][ \t\u00a0\u3000]*\u2009|\u2009[ \t\u00a0\u3000]*[\u4e00-\u9fa5]/.test(s);

  function exempt(t) {
    for (let n = t.parentNode; n; n = n.parentNode || n.host) {
      if (n.nodeType !== 1) continue;
      if (EXEMPT.has(n.localName.toUpperCase()) || n.classList?.contains('kf-mono')) return true;
    }
    return false;
  }

  let fixing = false;
  function fixText(t) {
    if (!t || t.nodeType !== 3 || !hasNear(t.data) || exempt(t) || URLISH.test(t.data)) return;
    const raw = hasThin(t.data) ? t.data.split(String.fromCharCode(0x2009)).join(' ') : t.data;
    const next = mix(raw);
    if (next !== t.data) t.data = next;
  }
  function fixTree(n) {
    if (n.nodeType === 3) return fixText(n);
    if (n.nodeType !== 1 && n.nodeType !== 11) return;
    const w = document.createTreeWalker(n, NodeFilter.SHOW_TEXT);
    let t;
    while ((t = w.nextNode())) fixText(t);
  }
  /* 拖拽排序插件(lit 的 live())会插入「活值重写」注释:把注释包着的文本再过一遍 */
  const mo = new MutationObserver((list) => {
    if (fixing) return;
    fixing = true;
    try {
      for (const m of list) {
        if (m.type === 'characterData') fixText(m.target);
        else {
          for (const n of m.addedNodes) { fixTree(n); if (n.nodeType === 1) { if (n.shadowRoot) observe(n.shadowRoot); for (const x of n.querySelectorAll('*')) if (x.shadowRoot) observe(x.shadowRoot); } }
        }
      }
    } finally {
      fixing = false;
    }
  });
  const seen = new WeakSet();
  function observe(root) {
    if (!root || seen.has(root)) return;
    seen.add(root);
    mo.observe(root, { childList: true, subtree: true, characterData: true });
    for (const el of root.querySelectorAll('*')) if (el.shadowRoot) observe(el.shadowRoot);
  }
  window.__kfCjk = {
    mix,
    hasThin,
    install() {
      observe(document);
      // 自定义元素后挂上来的 shadow root 也要进观察范围
      /* document.querySelectorAll 跨不过 shadow 边界:要递归进每一层 shadow root 找宿主 */
      const tick = () => {
        const seen = new Set();
        const scan = (root) => { for (const el of root.querySelectorAll('*')) { if (!el.shadowRoot || seen.has(el.shadowRoot)) continue; seen.add(el.shadowRoot); observe(el.shadowRoot); scan(el.shadowRoot); } };
        scan(document);
      };
      tick();
      setInterval(tick, 500);
      customElements.whenDefined('kf-app').then(tick);
    },
  };
  window.__kfCjk.install();
})();