Files
cjk-autospace/dist/juzhen.iife.js
T

3202 lines
101 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
var Juzhen = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
createJuzhen: () => createJuzhen,
normalizeOptions: () => normalizeOptions
});
// src/core/unicode.ts
var CJK = "\u2E80-\u2EFF\u2F00-\u2FDF\u3040-\u309F\u30A0-\u30FA\u30FC-\u30FF\u3100-\u312F\u3200-\u32FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B73F}\\u{2B740}-\\u{2B81F}\\u{2B820}-\\u{2CEAF}\\u{2CEB0}-\\u{2EBEF}\\u{2EBF0}-\\u{2EE5F}\\u{2F800}-\\u{2FA1F}\\u{30000}-\\u{3134F}\\u{31350}-\\u{323AF}\\u{323B0}-\\u{3347F}";
var VARIATION_SELECTORS = "\\uFE00-\\uFE0F\\u{E0100}-\\u{E01EF}";
function codePointAtUtf16(text, offset) {
const value = text.codePointAt(offset);
return value === void 0 ? "" : String.fromCodePoint(value);
}
function previousCodePointStart(text, offset) {
if (offset <= 0) {
return 0;
}
const last = text.charCodeAt(offset - 1);
if (last >= 56320 && last <= 57343 && offset >= 2) {
const first = text.charCodeAt(offset - 2);
if (first >= 55296 && first <= 56319) {
return offset - 2;
}
}
return offset - 1;
}
function isVariationSelector(ch) {
const value = ch.codePointAt(0);
return value !== void 0 && (value >= 65024 && value <= 65039 || value >= 917760 && value <= 917999);
}
function variationSequenceAtUtf16(text, offset) {
const base = codePointAtUtf16(text, offset);
if (!base || isVariationSelector(base)) {
return base;
}
const selector = codePointAtUtf16(text, offset + base.length);
return isVariationSelector(selector) ? base + selector : base;
}
function previousVariationSequenceStart(text, offset) {
const last = previousCodePointStart(text, offset);
const lastChar = codePointAtUtf16(text, last);
if (!isVariationSelector(lastChar) || last <= 0) {
return last;
}
const base = previousCodePointStart(text, last);
return isVariationSelector(codePointAtUtf16(text, base)) ? last : base;
}
function splitVariationSequences(text) {
const out = [];
for (let offset = 0; offset < text.length; ) {
const sequence = variationSequenceAtUtf16(text, offset);
out.push(sequence);
offset += sequence.length;
}
return out;
}
var ALNUM = "A-Za-z0-9";
var ALPHA = "A-Za-z";
var WEST_SHARED = "0-9\\$%\\^&\\*\\-\\+\\\\=\\u0370-\\u03ff\\u0100-\\u024f\\u0400-\\u04ff\\u00a1-\\u00b6\\u00b8-\\u00ff\\u20a0-\\u20bf\\u2150-\\u21ff\\u2200-\\u22ff\\u2700-\\u27bf";
var ANS_AFTER_CJK = ALPHA + "@" + WEST_SHARED;
var ANS_BEFORE_CJK = ALPHA + WEST_SHARED;
var BD_STOP = "\u3002\uFF0E\uFF01\uFF1F";
var BD_PAUSE = "\uFF0C\u3001\uFF1B\uFF1A";
var BD_OPEN = "\u300C\u300E\uFF08\u300A\u3008\u3010\u3016\u3014\uFF3B\uFF5B\u201C\u2018";
var BD_CLOSE = "\u300D\u300F\uFF09\u300B\u3009\u3011\u3017\u3015\uFF3D\uFF5D\u201D\u2019";
var BD_MIDDLE = "\xB7\u30FB\u2027";
var BD_LIGA = "\u2026\u2014";
var BD_ORDER = [
"stop",
"pause",
"open",
"close",
"middle",
"liga"
];
var DEFAULT_RULESET = {
cjk: CJK,
ansAfter: ANS_AFTER_CJK,
ansBefore: ANS_BEFORE_CJK,
biaodian: {
stop: BD_STOP,
pause: BD_PAUSE,
open: BD_OPEN,
close: BD_CLOSE,
middle: BD_MIDDLE,
liga: BD_LIGA
}
};
function resolveRuleset(override) {
if (!override) {
return DEFAULT_RULESET;
}
const west = override.western || "";
const bd = { ...DEFAULT_RULESET.biaodian };
if (override.biaodian) {
for (const sub of BD_ORDER) {
const extra = override.biaodian[sub];
if (extra) {
bd[sub] = bd[sub] + extra;
}
}
}
return {
cjk: CJK + (override.cjk || ""),
ansAfter: ANS_AFTER_CJK + west,
ansBefore: ANS_BEFORE_CJK + west,
biaodian: bd
};
}
function classifyBiaodian(ch, biaodian = DEFAULT_RULESET.biaodian) {
const base = codePointAtUtf16(ch, 0);
for (const sub of BD_ORDER) {
if (biaodian[sub].indexOf(base) >= 0) {
return "bd-" + sub;
}
}
return null;
}
function blankSides(bdClass) {
switch (bdClass) {
case "bd-open": {
return { left: true, right: false };
}
case "bd-close":
case "bd-pause":
case "bd-stop": {
return { left: false, right: true };
}
default: {
return { left: false, right: false };
}
}
}
var BRACKET_OPEN_RIGHT = "\u300C\u3008\u201C\u2018";
var BRACKET_CLOSE_LEFT = "\u300D\u3009\u201D\u2019";
var BIAS_BRACKETS = (() => {
const t = {};
for (const ch of BD_OPEN) {
t[ch] = BRACKET_OPEN_RIGHT.indexOf(ch) >= 0 ? "right" : "center";
}
for (const ch of BD_CLOSE) {
t[ch] = BRACKET_CLOSE_LEFT.indexOf(ch) >= 0 ? "left" : "center";
}
return t;
})();
var BIAS_HANS = {
"\uFF0C": "left",
"\u3001": "left",
"\u3002": "left",
"\uFF0E": "left",
"\uFF1B": "left",
"\uFF1A": "left",
"\uFF01": "left",
"\uFF1F": "left"
};
var BIAS_HANT = {
"\uFF0C": "center",
"\u3001": "center",
"\u3002": "center",
"\uFF0E": "center",
"\uFF1B": "center",
"\uFF1A": "center",
"\uFF01": "center",
"\uFF1F": "center"
};
var BIAS_DEFAULT = {
"zh-Hans": BIAS_HANS,
"zh-Hant": BIAS_HANT,
"ja": BIAS_HANT,
// 日文點號慣例居中,沿用繁體表
"other": BIAS_HANT
};
function inkBias(ch, langClass, overrides) {
const ov = overrides ? overrides[langClass] : void 0;
if (ov && ov[ch]) {
return ov[ch];
}
const bracket = BIAS_BRACKETS[ch];
if (bracket) {
return bracket;
}
const t = BIAS_DEFAULT[langClass] || BIAS_DEFAULT.other;
return t[ch] || null;
}
// src/core/finder.ts
var SVG_NS = "http://www.w3.org/2000/svg";
var Finder = class {
constructor(opts) {
this.opts = opts;
this.styleSet = opts.finder.styleInlines;
this.blockSet = opts.finder.blockTags;
this.skipSet = opts.finder.skipTags;
this.pillSelector = opts.finder.pillSelector;
this.isolateSelector = opts.finder.isolateSelector;
this.skipAttr = opts.finder.skipAttribute;
this.userIsSkipped = opts.finder.isSkipped;
this.levelTextSelector = opts.levelText;
this.avoidSelector = opts.scope.avoid;
this.includeSelector = opts.scope.include;
this.featureCache = /* @__PURE__ */ new WeakMap();
this.levelCache = /* @__PURE__ */ new WeakMap();
this.categoryCache = /* @__PURE__ */ new WeakMap();
this.pillAncestorCache = /* @__PURE__ */ new WeakMap();
this.avoidAncestorCache = /* @__PURE__ */ new WeakMap();
}
/**
* 清除 el 及其**整棵子樹**之元素鍵快取(C1 失效原語)。持久化 Finder(跨 render 復用快取)
* 下,元素之屬性或內容變化會令其分類(category)/級別(level)/功能(feature)/祖先游走
* (pill/avoid ancestor)快取失效。**須清整棵子樹、非僅該元素**:level/feature/祖先游走
* 之結果依賴祖先鏈,某祖先之 data-jz-level/data-juzhen/class 變化會令其**後代**之快取值變脏,
* 即便後代自身未動。querySelectorAll('*') 走子樹(O(子樹),native、無遞迴棧風險)。移除之元素
* 由 WeakMap 自動回收,無需顯式清。 */
invalidate(el) {
this.forget(el);
el.querySelectorAll("*").forEach((c) => this.forget(c));
}
forget(e) {
this.categoryCache.delete(e);
this.pillAncestorCache.delete(e);
this.avoidAncestorCache.delete(e);
this.levelCache.delete(e);
this.featureCache.delete(e);
}
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
pillMatches(el) {
return !!(el.matches && this.pillSelector && el.matches(this.pillSelector));
}
/** 該元素是否匹配 isolate 選擇器(三分類之「隔離」:帶盒樣式之行內,如背景色
* <mark>)。邊界口徑同 pill(斷 run/斷相鄰/不可切分/兩端補隙),內部照常處理。 */
isolateMatches(el) {
return !!(el.matches && this.isolateSelector && el.matches(this.isolateSelector));
}
/** 節點是否位於 pill **內部**(僅查祖先,不含自身)。pill 為整體元素:內部對
* 一切 pass 不可見——不 charify、不收 run、不切分、不於其內插間隙(下游問題六:
* .katex 內部曾被 jiya/spacing 直接變更)。 */
insidePill(node) {
const p = node.parentElement;
return p ? this.ancestorOrSelfPill(p) : false;
}
/** el 或其祖先鏈任一為 pill(記憶化)。沿祖先鏈迭代(非遞迴,任意深度棧安全)至最近之
* 已快取/pill 命中/鏈頂,途中非 pill 元素入棧、以斷點之值一次回填——每元素恰算一次,
* 兄弟共用祖先鏈快取。insidePill 為 pillMatches 之最大來源(S3 延伸)。 */
ancestorOrSelfPill(el) {
const chain = [];
let cur = el;
let base = false;
while (cur) {
const c = this.pillAncestorCache.get(cur);
if (c !== void 0) {
base = c;
break;
}
if (this.pillMatches(cur)) {
this.pillAncestorCache.set(cur, true);
base = true;
break;
}
chain.push(cur);
cur = cur.parentElement;
}
for (const e of chain) {
this.pillAncestorCache.set(e, base);
}
return base;
}
/**
* 行內元素分類(§4.1)——一元素對排版之口徑,**恰屬其一**。**優先序**(依序短路,即多
* 選擇器同命中時之定案規則):`avoid > pill > isolate > block > transparent > opaque`
* (opaque=未在 styleInlines 之未知行內標籤,嚴格白名單下當隔離式邊界)。故:同時命中
* pill 與 isolate → **pill**(pill 覆蓋 isolate);帶 data-jz-skip 之 pill/isolate → **avoid**
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
* transparent(結構切分為獨立層,故更窄)。
*
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
* 另議)。
*/
category(el) {
const cached = this.categoryCache.get(el);
if (cached) {
return cached;
}
const result = this.computeCategory(el);
this.categoryCache.set(el, result);
return result;
}
computeCategory(el) {
if (this.avoidsSelf(el)) {
return "avoid";
}
if (this.pillMatches(el)) {
return "pill";
}
if (this.isolateMatches(el)) {
return "isolate";
}
if (this.blockSet.has(el.nodeName)) {
return "block";
}
if (this.isTransparentTag(el)) {
return "transparent";
}
return "opaque";
}
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
* 終止、而**跳過**它續找更前之字(`汉字。<code>` 綁定後 pill 左鄰字應為「。」卻誤取「字」
* 補 jz-hws);jz-char 僅含標點(非 pillNeighbor),透明後 descend 回傳標點、不補隙。 */
isTransparentTag(el) {
const name = el.nodeName;
return this.styleSet.has(name) || name === "JZ-HWS" || name === "JZ-JINZE" || name === "JZ-CHAR" || name === "JZ-INNER";
}
/** el 是否為邏輯文本流可透明穿越者(= category "transparent")。 */
isTransparent(el) {
return this.category(el) === "transparent";
}
/** 該元素**自身**是否為 avoid 邊界。avoid 之七種來源,依序短路判定、任一命中即是:
* ① isSkipped 謂詞(消費端函數) ② skipTags 標籤名單(PRE/SAMP/SCRIPT…,可覆寫)
* ③ SVG 命名空間 ④ contentEditable
* ⑤ data-jz-skip 屬性(內建) ⑥ skipAttribute(消費端指定屬性名)
* ⑦ scope.avoid 選擇器
* **此為自身判定**,供遍歷原語(collectRuns/jiyaAdjacency/canSplit/isTransparent)於
* 下探時逐元素自判(祖先由遞迴涵蓋)。**一次性**問「某節點是否被 avoid(自身或祖先)」
* 請改用 isAvoided(單一組合入口),勿只用本方法而漏判祖先、或只用 isAvoided 而漏判自身
* (下游問題六/階段一 #2:漏一半致 avoid 對自身形同虛設)。 */
avoidsSelf(el) {
if (this.userIsSkipped && this.userIsSkipped(el)) {
return true;
}
if (this.skipSet.has(el.nodeName)) {
return true;
}
if (el.namespaceURI === SVG_NS) {
return true;
}
if (el.isContentEditable) {
return true;
}
if (el.hasAttribute("data-jz-skip")) {
return true;
}
if (this.skipAttr && el.hasAttribute(this.skipAttr)) {
return true;
}
if (this.avoidSelector && el.matches && el.matches(this.avoidSelector)) {
return true;
}
return false;
}
/** 某節點是否被 avoid——**自身即邊界**(元素經 avoidsSelf 之七源;非元素節點僅判
* isSkipped)**或**祖先鏈上任一為 avoid 邊界。此為「是否 avoid」之**單一組合入口(安全
* 預設)**:一次性檢查用此,免除「avoidsSelf ‖ isAvoided」易漏一半之誤用。遍歷原語逐
* 元素下探時則用 avoidsSelf(僅自身,祖先由遞迴涵蓋)。 */
isAvoided(node) {
if (node.nodeType === 1) {
return this.ancestorOrSelfAvoided(node);
}
if (this.userIsSkipped && this.userIsSkipped(node)) {
return true;
}
const p = node.parentElement;
return p ? this.ancestorOrSelfAvoided(p) : false;
}
/** el 或其祖先鏈任一為 avoid 邊界(記憶化,迭代回填,同 ancestorOrSelfPill)。 */
ancestorOrSelfAvoided(el) {
const chain = [];
let cur = el;
let base = false;
while (cur) {
const c = this.avoidAncestorCache.get(cur);
if (c !== void 0) {
base = c;
break;
}
if (this.avoidsSelf(cur)) {
this.avoidAncestorCache.set(cur, true);
base = true;
break;
}
chain.push(cur);
cur = cur.parentElement;
}
for (const e of chain) {
this.avoidAncestorCache.set(e, base);
}
return base;
}
/** scope.include 啟用判定:未設則恆 true;設則須有命中祖先(opt-in)。 */
inScope(node) {
if (!this.includeSelector) {
return true;
}
const el = node.nodeType === 1 ? node : node.parentElement;
if (!el) {
return false;
}
return !!(el.closest && el.closest(this.includeSelector));
}
/** 走訪 root 下所有「可處理」文本節點(已過濾 avoid / pill 內部 / 非作用域)。 */
eachTextNode(root, cb) {
const ownerDoc = root.ownerDocument || (typeof document !== "undefined" ? document : null);
if (!ownerDoc) {
return;
}
const walker = ownerDoc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const nodes = [];
let n = walker.nextNode();
while (n) {
const t = n;
if (!this.isAvoided(t) && !this.insidePill(t) && this.inScope(t)) {
nodes.push(t);
}
n = walker.nextNode();
}
for (const node of nodes) {
cb(node);
}
}
/** 走訪 root 下所有 block 元素(含 root 本身若為 block)。 */
eachBlock(root, cb) {
const sel = Array.from(this.blockSet).map((t) => t.toLowerCase()).join(",");
const blocks = [];
if (sel) {
root.querySelectorAll(sel).forEach((el) => blocks.push(el));
}
if (root.nodeType === 1 && this.blockSet.has(root.nodeName)) {
blocks.push(root);
}
for (const b of blocks) {
if (this.includeSelector && !(b.closest && b.closest(this.includeSelector))) {
continue;
}
cb(b);
}
}
/**
* 收集 block 之邏輯文本 run(架構 §4.1,I7):透明行內(isTransparent:styleInlines
* + wrap 類 jz-*)穿越、run 延續;block/pill/avoid 中斷 run;isolate 與**未知行內
* 標籤**(嚴格白名單)中斷 run 且內部自成獨立 run。回傳 text.length >= 2 之 run。
*/
collectRuns(block) {
const runs = [];
let curText = "";
let curMap = [];
const flush = () => {
if (curMap.length >= 2) {
runs.push({ text: curText, map: curMap });
}
curText = "";
curMap = [];
};
const visit = (node) => {
if (!node) {
return;
}
if (node.nodeType === 3) {
const t = node;
if (this.isAvoided(t) || !this.inScope(t)) {
return;
}
const data = t.data;
for (let i = 0; i < data.length; ) {
const ch = variationSequenceAtUtf16(data, i);
curText += ch;
const part = { textNode: t, start: i, end: i + ch.length };
const previous = curMap[curMap.length - 1];
if (isVariationSelector(ch) && previous && !isVariationSelector(codePointAtUtf16(previous.text, 0)) && !isVariationSelector(codePointAtUtf16(
previous.text,
previousCodePointStart(previous.text, previous.text.length)
))) {
previous.text += ch;
previous.parts.push(part);
} else {
curMap.push({ text: ch, parts: [part] });
}
i += ch.length;
}
return;
}
if (node.nodeType !== 1) {
return;
}
const el = node;
const recurse = () => {
let c2 = el.firstChild;
while (c2) {
visit(c2);
c2 = c2.nextSibling;
}
};
switch (this.category(el)) {
case "avoid":
case "block":
case "pill": {
flush();
return;
}
case "transparent": {
recurse();
return;
}
default: {
flush();
recurse();
flush();
return;
}
}
};
let c = block.firstChild;
while (c) {
visit(c);
c = c.nextSibling;
}
flush();
return runs;
}
/**
* pill 之邏輯鄰字(架構 §4.1):經樣式鏈遞迴,遇 pill/block/avoid/間隙
* marker 即止。direction = -1 取前鄰字,+1 取後鄰字。
*/
adjacentLogicalChar(el, direction) {
const pickEnd = (tn) => {
if (direction === -1) {
const offset = previousVariationSequenceStart(tn.data, tn.data.length);
return {
textNode: tn,
offset,
ch: variationSequenceAtUtf16(tn.data, offset)
};
}
return { textNode: tn, offset: 0, ch: variationSequenceAtUtf16(tn.data, 0) };
};
const descend = (into) => {
let n2 = direction === -1 ? into.lastChild : into.firstChild;
while (n2) {
if (n2.nodeType === 3 && n2.data.length > 0) {
return pickEnd(n2);
}
if (n2.nodeType === 1) {
if (!this.isTransparent(n2)) {
return "blocked";
}
const r = descend(n2);
if (r) {
return r;
}
}
n2 = direction === -1 ? n2.previousSibling : n2.nextSibling;
}
return null;
};
let n = direction === -1 ? el.previousSibling : el.nextSibling;
let p = el.parentNode;
for (; ; ) {
while (n) {
if (n.nodeType === 3) {
if (n.data.length > 0) {
return pickEnd(n);
}
n = direction === -1 ? n.previousSibling : n.nextSibling;
continue;
}
if (n.nodeType === 1) {
const e = n;
if (!this.isTransparent(e)) {
return null;
}
const r = descend(e);
if (r === "blocked") {
return null;
}
if (r) {
return r;
}
n = direction === -1 ? e.previousSibling : e.nextSibling;
continue;
}
n = direction === -1 ? n.previousSibling : n.nextSibling;
}
if (!p || p.nodeType !== 1 || !this.isTransparent(p)) {
return null;
}
const pe = p;
n = direction === -1 ? pe.previousSibling : pe.nextSibling;
p = pe.parentNode;
}
}
/**
* 節點之功能級別(§6.5.0a)。自 node 向上找**最近**之顯式信號:元素屬性
* `data-jz-level="text|paragraph"`(最優先),或命中 `level.text` 選擇器(→ text);
* 皆無則預設 paragraph(全功能)。結果以解析後之元素為鍵快取。
*/
levelFor(node) {
const start = node.nodeType === 1 ? node : node.parentElement;
if (!start) {
return "paragraph";
}
const cached = this.levelCache.get(start);
if (cached) {
return cached;
}
let el = start;
let result = "paragraph";
while (el) {
const attr = el.getAttribute ? el.getAttribute("data-jz-level") : null;
if (attr === "text" || attr === "paragraph") {
result = attr;
break;
}
if (this.levelTextSelector && el.matches && el.matches(this.levelTextSelector)) {
result = "text";
break;
}
el = el.parentElement;
}
this.levelCache.set(start, result);
return result;
}
/**
* 某級別之 pass 是否可作用於該節點(§6.5.0a gate)。文本級 pass 不受限(恆 true);
* 段落級 pass 僅作用於段落級元素(text-level 子樹跳過)。與 featureEnabledFor 疊加:
* level 為粗粒度語義組(先判),blocks/屬性為細粒度逐功能(後調)。
*/
levelAllows(node, passLevel) {
if (passLevel === "text") {
return true;
}
return this.levelFor(node) === "paragraph";
}
/**
* 解析節點對某功能之有效啟用狀態(§3.6,per-node 閘)。向上找最近帶
* 功能指令之祖先(data-juzhen 白名單 / data-juzhen-off 黑名單 / 命中
* blocks 選擇器),無則回退全域 features。結果以元素為鍵快取。
*/
featureEnabledFor(node, name) {
const el = node.nodeType === 1 ? node : node.parentElement;
const globalOn = this.globalFeature(name);
if (!el) {
return globalOn;
}
const cached = this.featureCache.get(el);
if (cached && name in cached) {
return cached[name];
}
const result = this.resolveFeature(el, name, globalOn);
const bucket = cached || {};
bucket[name] = result;
this.featureCache.set(el, bucket);
return result;
}
globalFeature(name) {
const f = this.opts.features;
return !!f[name];
}
resolveFeature(start, name, globalOn) {
let el = start;
while (el) {
if (el.hasAttribute("data-juzhen")) {
const list = (el.getAttribute("data-juzhen") || "").split(/\s+/).filter(Boolean);
return list.indexOf(name) >= 0;
}
if (el.hasAttribute("data-juzhen-off")) {
const off = (el.getAttribute("data-juzhen-off") || "").split(/\s+/).filter(Boolean);
if (off.indexOf(name) >= 0) {
return false;
}
}
for (const rule of this.opts.blocks) {
if (rule.selector && el.matches && el.matches(rule.selector)) {
return rule.features.indexOf(name) >= 0;
}
}
el = el.parentElement;
}
return globalOn;
}
};
// src/core/dom.ts
var KIND_ATTR = "data-jz-kind";
var PASS_ATTR = "data-jz";
var WRAP_TAGS = /* @__PURE__ */ new Map([
["spacing", /* @__PURE__ */ new Set(["JZ-HWS"])],
["jiya", /* @__PURE__ */ new Set(["JZ-CHAR", "JZ-INNER"])],
["jinze", /* @__PURE__ */ new Set(["JZ-JINZE"])],
["longWord", /* @__PURE__ */ new Set(["SPAN"])],
["orphan", /* @__PURE__ */ new Set(["JZ-ORPHAN"])]
]);
var SPLITOFF_TAGS = /* @__PURE__ */ new Set([
"STRONG",
"EM",
"B",
"I",
"U",
"S",
"SPAN",
"SMALL",
"INS",
"DEL",
"SUB",
"SUP"
]);
var ATTRIBUTE_WRITERS = /* @__PURE__ */ new WeakMap();
var OWNER_ATTRIBUTES = /* @__PURE__ */ new WeakMap();
function trackOwnedAttribute(owner, name, el) {
let attrs = OWNER_ATTRIBUTES.get(owner);
if (!attrs) {
attrs = /* @__PURE__ */ new Map();
OWNER_ATTRIBUTES.set(owner, attrs);
}
let elements = attrs.get(name);
if (!elements) {
elements = /* @__PURE__ */ new Set();
attrs.set(name, elements);
}
elements.add(el);
}
function untrackOwnedAttribute(owner, name, el) {
const attrs = OWNER_ATTRIBUTES.get(owner);
const elements = attrs?.get(name);
if (!elements) {
return;
}
elements.delete(el);
if (elements.size === 0) {
attrs.delete(name);
}
}
function doc() {
if (typeof document === "undefined") {
throw new Error("juzhen: \u7121 document\uFF08\u9700\u65BC\u700F\u89BD\u5668\u6216 jsdom \u74B0\u5883\u57F7\u884C\uFF09\u3002");
}
return document;
}
function createJz(ownedElements, tag, pass, kind, opts = {}) {
const el = doc().createElement(tag);
ownedElements.add(el);
el.setAttribute(PASS_ATTR, pass);
el.setAttribute(KIND_ATTR, kind);
if (opts.classes) {
const list = Array.isArray(opts.classes) ? opts.classes : [opts.classes];
for (const c of list) {
if (c) {
el.classList.add(c);
}
}
}
if (opts.text !== void 0) {
el.textContent = opts.text;
}
return el;
}
function setOwnedAttribute(el, name, value, owner) {
let attrs = ATTRIBUTE_WRITERS.get(el);
if (!attrs) {
attrs = /* @__PURE__ */ new Map();
ATTRIBUTE_WRITERS.set(el, attrs);
}
let state = attrs.get(name);
if (!state) {
state = { original: el.getAttribute(name), writers: [] };
attrs.set(name, state);
} else {
const top = state.writers[state.writers.length - 1];
if (top && el.getAttribute(name) !== top.value) {
for (const writer of state.writers) {
untrackOwnedAttribute(writer.owner, name, el);
}
state = { original: el.getAttribute(name), writers: [] };
attrs.set(name, state);
}
}
state.writers = state.writers.filter((writer) => writer.owner !== owner);
state.writers.push({ owner, value });
trackOwnedAttribute(owner, name, el);
el.setAttribute(name, value);
}
function revertOwnedAttribute(el, name, owner) {
const attrs = ATTRIBUTE_WRITERS.get(el);
const state = attrs?.get(name);
if (!state) {
untrackOwnedAttribute(owner, name, el);
return;
}
const index = state.writers.findIndex((writer) => writer.owner === owner);
if (index < 0) {
untrackOwnedAttribute(owner, name, el);
return;
}
const wasTop = index === state.writers.length - 1;
const top = state.writers[state.writers.length - 1];
const externallyChanged = el.getAttribute(name) !== top.value;
state.writers.splice(index, 1);
untrackOwnedAttribute(owner, name, el);
if (!externallyChanged && wasTop) {
const next = state.writers[state.writers.length - 1];
const restored = next ? next.value : state.original;
if (restored === null) {
el.removeAttribute(name);
} else {
el.setAttribute(name, restored);
}
}
if (state.writers.length === 0) {
attrs.delete(name);
}
}
function revertOwnedAttributes(root, name, owner) {
const elements = OWNER_ATTRIBUTES.get(owner)?.get(name);
if (!elements) {
return;
}
for (const el of Array.from(elements)) {
if (el === root || root.contains(el)) {
revertOwnedAttribute(el, name, owner);
}
}
}
function knownProduct(pass, kind, el) {
if (kind === "wrap") {
return !!WRAP_TAGS.get(pass)?.has(el.nodeName);
}
if (kind === "splitoff") {
return (pass === "jinze" || pass === "orphan") && SPLITOFF_TAGS.has(el.nodeName);
}
return false;
}
function revertPass(pass, root, ownedElements) {
const nodes = Array.from(
root.querySelectorAll("[" + PASS_ATTR + '="' + pass + '"]')
).filter((el) => ownedElements.has(el));
const touched = /* @__PURE__ */ new Set();
for (let i = nodes.length - 1; i >= 0; i -= 1) {
const el = nodes[i];
const parent = el.parentNode;
if (!parent) {
continue;
}
const kind = el.getAttribute(KIND_ATTR);
if (!knownProduct(pass, kind, el)) {
continue;
}
if (kind === "splitoff") {
continue;
}
touched.add(parent);
while (el.firstChild) {
parent.insertBefore(el.firstChild, el);
}
parent.removeChild(el);
ownedElements.delete(el);
}
const splitoffs = Array.from(
root.querySelectorAll(
"[" + PASS_ATTR + '="' + pass + '"][' + KIND_ATTR + '="splitoff"]'
)
).filter((el) => ownedElements.has(el) && knownProduct(pass, "splitoff", el));
for (const so of splitoffs) {
const parent = so.parentNode;
if (!parent) {
continue;
}
touched.add(parent);
const prev = so.previousSibling;
if (prev && prev.nodeType === 1 && prev.nodeName === so.nodeName) {
while (so.firstChild) {
prev.appendChild(so.firstChild);
}
parent.removeChild(so);
} else {
while (so.firstChild) {
parent.insertBefore(so.firstChild, so);
}
parent.removeChild(so);
}
ownedElements.delete(so);
}
for (const p of touched) {
if (p.normalize) {
p.normalize();
}
}
}
var BD_CLASSES = ["bd-open", "bd-close", "bd-pause", "bd-stop", "bd-middle", "bd-liga"];
function bdClassOf(el) {
for (const c of BD_CLASSES) {
if (el.classList.contains(c)) {
return c;
}
}
return null;
}
function isJz(node, pass) {
if (!node || node.nodeType !== 1) {
return false;
}
const el = node;
if (!el.hasAttribute(PASS_ATTR)) {
return false;
}
return pass === void 0 || el.getAttribute(PASS_ATTR) === pass;
}
// src/core/pass.ts
function runPasses(passes, ctx) {
const enabled = passes.filter((p) => p.enabled(ctx.options));
const charify = enabled.filter((p) => p.kind === "charify").sort((a, b) => a.order - b.order);
const standalone = enabled.filter((p) => p.kind === "standalone").sort((a, b) => a.order - b.order);
if (charify.length > 0) {
runCharify(charify, ctx);
}
for (const p of standalone) {
p.render(ctx);
}
}
function revertPasses(passes, ctx) {
const ordered = passes.slice().sort((a, b) => b.order - a.order);
for (const p of ordered) {
p.revert(ctx);
}
}
function runCharify(passes, ctx) {
const work = [];
ctx.finder.eachTextNode(ctx.root, (node) => {
let requests = [];
for (const p of passes) {
if (!ctx.finder.levelAllows(node, p.level)) {
continue;
}
const got = p.collect(node, ctx);
if (got.length > 0) {
requests = requests.concat(got);
}
}
if (requests.length > 0) {
work.push({ node, requests });
}
});
for (const item of work) {
applyRequests(item.node, item.requests, ctx.ownedElements);
}
}
function applyRequests(node, requests, ownedElements) {
const parent = node.parentNode;
if (!parent) {
return;
}
const sorted = requests.slice().sort((a, b) => a.start - b.start || a.end - b.end);
const data = node.data;
const ownerDoc = node.ownerDocument;
if (!ownerDoc) {
return;
}
const frag = ownerDoc.createDocumentFragment();
let cursor = 0;
for (const req of sorted) {
if (req.start < cursor) {
continue;
}
if (req.start < 0 || req.end > data.length || req.end <= req.start) {
continue;
}
if (req.start > cursor) {
frag.appendChild(ownerDoc.createTextNode(data.slice(cursor, req.start)));
}
const built = req.build(data.slice(req.start, req.end));
ownedElements.add(built);
frag.appendChild(built);
cursor = req.end;
}
if (cursor === 0) {
return;
}
if (cursor < data.length) {
frag.appendChild(ownerDoc.createTextNode(data.slice(cursor)));
}
parent.replaceChild(frag, node);
}
// src/core/locale.ts
var DEFAULT_POLICY = {
"zh-Hant": "quanjiao",
"zh-Hans": "kaiming",
"ja": "quanjiao",
"other": "quanjiao"
};
function effectiveLang(node, fallback) {
let p = node.parentNode;
while (p && p.nodeType === 1) {
const el = p;
const v = el.getAttribute("lang");
if (v) {
return v.toLowerCase();
}
p = p.parentNode;
}
return (fallback || "").toLowerCase();
}
function classify(lang) {
const l = (lang || "").toLowerCase();
if (l === "ja" || l.indexOf("ja-") === 0) {
return "ja";
}
if (l === "zh" || l.indexOf("zh-") === 0) {
if (l.indexOf("hant") >= 0) {
return "zh-Hant";
}
if (l.indexOf("hans") >= 0) {
return "zh-Hans";
}
if (/-(tw|hk|mo)\b/.test(l)) {
return "zh-Hant";
}
if (l === "zh") {
return "zh-Hant";
}
return "zh-Hans";
}
return "other";
}
function styleFor(langClass, config) {
const perLang = config.policy ? config.policy[langClass] : void 0;
if (perLang) {
return perLang;
}
if (config.style) {
return config.style;
}
return DEFAULT_POLICY[langClass];
}
function isPunctStyle(v) {
return v === "quanjiao" || v === "kaiming" || v === "banjiao";
}
function resolveStyleOverride(node, stylePolicy) {
let el = node.nodeType === 1 ? node : node.parentElement;
while (el) {
const attr = el.getAttribute ? el.getAttribute("data-jz-style") : null;
if (isPunctStyle(attr)) {
return attr;
}
if (stylePolicy) {
for (const rule of stylePolicy) {
if (rule.selector && el.matches && el.matches(rule.selector)) {
return rule.style;
}
}
}
el = el.parentElement;
}
return null;
}
function resolveStyle(node, config) {
const override = resolveStyleOverride(node, config.stylePolicy);
if (override) {
return override;
}
const lang = effectiveLang(node, config.default);
return styleFor(classify(lang), config);
}
// src/typeset/jiya.ts
var PASS = "jiya";
function baselineHalf(style, bdClass) {
if (style === "quanjiao") {
return false;
}
if (style === "banjiao") {
return bdClass === "bd-pause" || bdClass === "bd-stop" || bdClass === "bd-open" || bdClass === "bd-close";
}
return bdClass === "bd-pause" || bdClass === "bd-open" || bdClass === "bd-close";
}
function inJzChar(node) {
let p = node.parentNode;
while (p && p.nodeType === 1) {
const name = p.nodeName;
if (name === "JZ-CHAR" || name === "JZ-INNER") {
return true;
}
p = p.parentNode;
}
return false;
}
var jiyaPass = {
name: PASS,
kind: "charify",
order: 20,
level: "text",
enabled: (o) => o.features.jiya,
collect(node, ctx) {
if (inJzChar(node)) {
return [];
}
if (!ctx.finder.featureEnabledFor(node, PASS)) {
return [];
}
const data = node.data;
const out = [];
let style = null;
let langClass = null;
const biasOverrides = ctx.options.jiyaConfig.bias;
const biaodian = ctx.options.ruleset.biaodian;
for (let i = 0; i < data.length; ) {
const ch = variationSequenceAtUtf16(data, i);
const bdClass = classifyBiaodian(ch, biaodian);
if (!bdClass) {
i += ch.length;
continue;
}
if (style === null) {
style = resolveStyle(node, ctx.options.locale);
}
if (langClass === null) {
langClass = classify(effectiveLang(node, ctx.options.locale.default));
}
const classes = baselineHalf(style, bdClass) ? [bdClass, "jz-half"] : [bdClass];
const bias = inkBias(codePointAtUtf16(ch, 0), langClass, biasOverrides);
if (bias) {
classes.push("jz-ink-" + bias);
}
out.push({
start: i,
end: i + ch.length,
pass: PASS,
build: (text) => {
const outer = createJz(
ctx.ownedElements,
"jz-char",
PASS,
"wrap",
{ classes }
);
const inner = createJz(
ctx.ownedElements,
"jz-inner",
PASS,
"wrap",
{ text }
);
outer.appendChild(inner);
return outer;
}
});
i += ch.length;
}
return out;
},
revert(ctx) {
revertPass(PASS, ctx.root, ctx.ownedElements);
}
};
var jiyaAdjacencyPass = {
name: "jiyaAdjacency",
kind: "standalone",
order: 25,
level: "text",
enabled: (o) => o.features.jiya,
render(ctx) {
const { finder } = ctx;
let prev = null;
const squeeze = (L, R) => {
if (!finder.featureEnabledFor(L, PASS)) {
return;
}
const lClass = bdClassOf(L);
const rClass = bdClassOf(R);
const lSides = blankSides(lClass);
const rSides = blankSides(rClass);
const stopBeforeOpen = lClass === "bd-stop" && rClass === "bd-open";
if (lSides.right && !stopBeforeOpen) {
L.classList.add("jz-half");
}
if (rSides.left) {
R.classList.add("jz-half");
}
};
const visit = (node) => {
for (let c = node.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3) {
if (c.data.length > 0) {
prev = null;
}
continue;
}
if (c.nodeType !== 1) {
continue;
}
const el = c;
const nm = el.nodeName;
if (nm === "JZ-CHAR") {
if (!ctx.ownedElements.has(el)) {
prev = null;
continue;
}
if (prev) {
squeeze(prev, el);
}
prev = el;
} else {
switch (finder.category(el)) {
case "avoid":
case "pill": {
prev = null;
break;
}
case "transparent": {
visit(el);
break;
}
default: {
prev = null;
visit(el);
prev = null;
break;
}
}
}
}
};
visit(ctx.root);
},
revert() {
}
};
// src/core/split.ts
var PASS_ATTR2 = "data-jz";
var KIND_ATTR2 = "data-jz-kind";
var SPLITTABLE = /* @__PURE__ */ new Set([
"STRONG",
"EM",
"B",
"I",
"U",
"S",
"SPAN",
"SMALL",
"INS",
"DEL",
"SUB",
"SUP"
]);
function canSplit(el, finder) {
return SPLITTABLE.has(el.nodeName) && !el.hasAttribute("id") && finder.category(el) === "transparent";
}
function firstText(el, finder) {
for (let c = el.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length > 0) {
return c;
}
if (c.nodeType === 1) {
if (!canSplit(c, finder)) {
return null;
}
const r = firstText(c, finder);
if (r) {
return r;
}
}
}
return null;
}
function lastText(el, finder) {
for (let c = el.lastChild; c; c = c.previousSibling) {
if (c.nodeType === 3 && c.data.length > 0) {
return c;
}
if (c.nodeType === 1) {
if (!canSplit(c, finder)) {
return null;
}
const r = lastText(c, finder);
if (r) {
return r;
}
}
}
return null;
}
function isTokenChar(ch, anyCjk) {
return !anyCjk.test(ch) && !/\s/.test(ch);
}
function leadingTokenEnd(d, anyCjk) {
const first = variationSequenceAtUtf16(d, 0);
if (anyCjk.test(first)) {
return first.length;
}
let e = first.length;
while (e < d.length) {
const ch = variationSequenceAtUtf16(d, e);
if (!isTokenChar(ch, anyCjk)) {
break;
}
e += ch.length;
}
return e;
}
function trailingTokenStart(d, anyCjk) {
let s = previousVariationSequenceStart(d, d.length);
if (anyCjk.test(variationSequenceAtUtf16(d, s))) {
return s;
}
while (s > 0) {
const previous = previousVariationSequenceStart(d, s);
if (!isTokenChar(variationSequenceAtUtf16(d, previous), anyCjk)) {
break;
}
s = previous;
}
return s;
}
function splitTreeAfter(top, textNode, offset, pass, finder, ownedElements) {
for (let a = textNode.parentNode; a && a.nodeType === 1; a = a.parentNode) {
if (!canSplit(a, finder)) {
return null;
}
if (a === top) {
break;
}
}
if (offset <= 0 && firstText(top, finder) === textNode) {
return null;
}
let rightAtLevel;
if (offset >= textNode.data.length) {
rightAtLevel = textNode.nextSibling;
} else if (offset <= 0) {
rightAtLevel = textNode;
} else {
rightAtLevel = textNode.splitText(offset);
}
let ancestor = textNode.parentNode;
let topClone = null;
while (ancestor && ancestor.nodeType === 1) {
const anc = ancestor;
let clone = null;
if (rightAtLevel) {
clone = anc.cloneNode(false);
clone.removeAttribute("id");
clone.setAttribute(PASS_ATTR2, pass);
clone.setAttribute(KIND_ATTR2, "splitoff");
ownedElements.add(clone);
let n = rightAtLevel;
while (n) {
const next = n.nextSibling;
clone.appendChild(n);
n = next;
}
}
if (anc === top) {
if (!clone) {
return null;
}
if (top.parentNode) {
top.parentNode.insertBefore(clone, top.nextSibling);
}
topClone = clone;
break;
}
if (clone) {
if (anc.parentNode) {
anc.parentNode.insertBefore(clone, anc.nextSibling);
}
rightAtLevel = clone;
} else {
rightAtLevel = anc.nextSibling;
}
ancestor = anc.parentNode;
}
return topClone;
}
function isolateBoundaryToken(el, side, anyCjk, pass, finder, ownedElements) {
if (!canSplit(el, finder)) {
return el;
}
const text = side === "tail" ? lastText(el, finder) : firstText(el, finder);
if (!text) {
return el;
}
const point = side === "tail" ? { textNode: text, offset: trailingTokenStart(text.data, anyCjk) } : { textNode: text, offset: leadingTokenEnd(text.data, anyCjk) };
const clone = splitTreeAfter(el, point.textNode, point.offset, pass, finder, ownedElements);
if (!clone) {
return el;
}
return side === "tail" ? clone : el;
}
function splitElementAt(el, textNode, offset, pass, finder, ownedElements) {
if (!canSplit(el, finder)) {
return null;
}
return splitTreeAfter(el, textNode, offset, pass, finder, ownedElements);
}
// src/typeset/jinze.ts
var PASS2 = "jinze";
var AVOID_HEAD = /* @__PURE__ */ new Set(["bd-close", "bd-pause", "bd-stop", "bd-middle", "bd-liga"]);
var AVOID_TAIL = /* @__PURE__ */ new Set(["bd-open"]);
function isCharEl(n) {
return !!n && n.nodeType === 1 && n.nodeName === "JZ-CHAR";
}
function firstMeaningfulChild(el) {
for (let c = el.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length === 0) {
continue;
}
return c;
}
return null;
}
function lastMeaningfulChild(el) {
for (let c = el.lastChild; c; c = c.previousSibling) {
if (c.nodeType === 3 && c.data.length === 0) {
continue;
}
return c;
}
return null;
}
function atEdge(node, el, side) {
return node === (side === "head" ? firstMeaningfulChild(el) : lastMeaningfulChild(el));
}
function edgeCharEl(node, side, finder) {
let n = node;
while (n && n.nodeType === 1) {
if (isCharEl(n)) {
return n;
}
if (!finder.isTransparent(n)) {
return null;
}
n = side === "head" ? firstMeaningfulChild(n) : lastMeaningfulChild(n);
}
return null;
}
function forbiddenBreak(a, b, finder) {
const head = edgeCharEl(b, "head", finder);
if (head) {
const c = bdClassOf(head);
if (c && AVOID_HEAD.has(c) && finder.featureEnabledFor(head, PASS2)) {
return true;
}
}
const tail = edgeCharEl(a, "tail", finder);
if (tail) {
const c = bdClassOf(tail);
if (c && AVOID_TAIL.has(c) && finder.featureEnabledFor(tail, PASS2)) {
return true;
}
}
return false;
}
function depthOf(node) {
let d = 0;
let p = node.parentNode;
while (p) {
d += 1;
p = p.parentNode;
}
return d;
}
function isTokenChar2(ch, anyCjk) {
return !anyCjk.test(ch) && !/\s/.test(ch);
}
function leadingTokenEnd2(d, anyCjk) {
const first = variationSequenceAtUtf16(d, 0);
if (anyCjk.test(first)) {
return first.length;
}
let e = first.length;
while (e < d.length) {
const ch = variationSequenceAtUtf16(d, e);
if (!isTokenChar2(ch, anyCjk)) {
break;
}
e += ch.length;
}
return e;
}
function trailingTokenStart2(d, anyCjk) {
let s = previousVariationSequenceStart(d, d.length);
if (anyCjk.test(variationSequenceAtUtf16(d, s))) {
return s;
}
while (s > 0) {
const previous = previousVariationSequenceStart(d, s);
if (!isTokenChar2(variationSequenceAtUtf16(d, previous), anyCjk)) {
break;
}
s = previous;
}
return s;
}
function splitBoundaries(t, needFirst, needLast, anyCjk) {
let node = t;
if (needFirst) {
const e = leadingTokenEnd2(node.data, anyCjk);
if (e < node.data.length) {
node = node.splitText(e);
}
}
if (needLast) {
const s = trailingTokenStart2(node.data, anyCjk);
if (s > 0) {
node.splitText(s);
}
}
}
function processParent(parent, finder, anyCjk, ownedElements) {
if (isJz(parent, PASS2)) {
return;
}
for (let c = parent.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 1 && c.nodeName === "JZ-JINZE") {
return;
}
}
const units = [];
for (let c = parent.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 3 && c.data.length > 0) {
units.push(c);
} else if (c.nodeType === 1) {
units.push(c);
}
}
const n = units.length;
if (n < 2) {
return;
}
const forbidden = [];
for (let i2 = 0; i2 < n - 1; i2 += 1) {
forbidden.push(forbiddenBreak(units[i2], units[i2 + 1], finder));
}
for (let i2 = 0; i2 < n; i2 += 1) {
const u = units[i2];
const needFirst = i2 > 0 && forbidden[i2 - 1];
const needLast = i2 < n - 1 && forbidden[i2];
if (!needFirst && !needLast) {
continue;
}
if (u.nodeType === 3) {
splitBoundaries(u, needFirst, needLast, anyCjk);
} else if (u.nodeType === 1) {
const el = u;
if (needLast) {
isolateBoundaryToken(el, "tail", anyCjk, PASS2, finder, ownedElements);
}
if (needFirst) {
isolateBoundaryToken(el, "head", anyCjk, PASS2, finder, ownedElements);
}
}
}
const kids = [];
for (let c = parent.firstChild; c; c = c.nextSibling) {
kids.push(c);
}
let i = 0;
while (i < kids.length) {
const run = [kids[i]];
while (i + 1 < kids.length && forbiddenBreak(kids[i], kids[i + 1], finder)) {
run.push(kids[i + 1]);
i += 1;
}
if (run.length >= 2) {
const group = createJz(ownedElements, "jz-jinze", PASS2, "wrap");
parent.insertBefore(group, run[0]);
for (const node of run) {
group.appendChild(node);
}
}
i += 1;
}
}
var jinzePass = {
name: PASS2,
kind: "standalone",
order: 40,
level: "paragraph",
enabled: (o) => o.features.jinze,
render(ctx) {
const { finder } = ctx;
const anyCjk = new RegExp("[" + ctx.options.ruleset.cjk + "]", "u");
const parents = /* @__PURE__ */ new Set();
ctx.root.querySelectorAll("jz-char").forEach((c) => {
if (!ctx.ownedElements.has(c)) {
return;
}
if (!finder.levelAllows(c, "paragraph")) {
return;
}
let child = c;
let parent = c.parentNode;
while (parent && parent.nodeType === 1) {
parents.add(parent);
const pe = parent;
if (!finder.isTransparent(pe)) {
break;
}
if (!atEdge(child, pe, "head") && !atEdge(child, pe, "tail")) {
break;
}
child = pe;
parent = pe.parentNode;
}
});
const ordered = Array.from(parents).sort((a, b) => depthOf(b) - depthOf(a));
for (const parent of ordered) {
processParent(parent, finder, anyCjk, ctx.ownedElements);
}
},
revert(ctx) {
revertPass(PASS2, ctx.root, ctx.ownedElements);
}
};
// src/typeset/levelmark.ts
var ATTR = "data-jz-block-level";
var levelMarkPass = {
name: "levelMark",
kind: "standalone",
order: 10,
level: "text",
// 不受段落級閘限制:text 級之塊亦須標記(標記本身即分級輸出)
enabled: () => true,
// 分類鉤子非排版功能,恆開(純屬性輸出、零版面影響)
render(ctx) {
const { finder, options } = ctx;
const blockSet = options.finder.blockTags;
const nearest = /* @__PURE__ */ new Map();
const nearestBlock = ((p) => {
const cached = nearest.get(p);
if (cached !== void 0) {
return cached;
}
let e = p;
let block = null;
while (e) {
if (blockSet.has(e.nodeName)) {
block = e;
break;
}
if (e === ctx.root) {
break;
}
e = e.parentElement;
}
nearest.set(p, block);
return block;
});
finder.eachTextNode(ctx.root, (t) => {
if (!/\S/.test(t.data)) {
return;
}
const p = t.parentElement;
const block = p ? nearestBlock(p) : null;
if (!block) {
return;
}
const level = finder.levelFor(block);
setOwnedAttribute(block, ATTR, level, ctx.ownerToken);
});
},
revert(ctx) {
revertOwnedAttributes(ctx.root, ATTR, ctx.ownerToken);
}
};
// src/typeset/lineedge.ts
var LE = "jz-half-le";
var ALL_EDGE = [LE];
var EPS = 2;
var HEAD_CLASSES = /* @__PURE__ */ new Set(["bd-open"]);
var TAIL_CLASSES = /* @__PURE__ */ new Set(["bd-close"]);
var observers = /* @__PURE__ */ new WeakMap();
function hasLayout() {
return typeof ResizeObserver !== "undefined";
}
function installLayout(root, owner, key, run) {
run();
let owners = observers.get(root);
if (!owners) {
owners = /* @__PURE__ */ new Map();
observers.set(root, owners);
}
let m = owners.get(owner);
if (!m) {
m = /* @__PURE__ */ new Map();
owners.set(owner, m);
}
const existing = m.get(key);
if (existing) {
existing.disconnect();
}
const ro = new ResizeObserver(() => run());
ro.observe(root);
m.set(key, ro);
const fonts = root.ownerDocument.fonts;
if (fonts && fonts.ready && typeof fonts.ready.then === "function") {
fonts.ready.then(run, () => void 0);
}
}
function uninstallLayout(root, owner, key) {
const owners = observers.get(root);
const m = owners?.get(owner);
if (!m) {
return;
}
const ro = m.get(key);
if (ro) {
ro.disconnect();
m.delete(key);
}
if (m.size === 0) {
owners.delete(owner);
}
}
var JZ_WRAP_TAGS = ["JZ-JINZE", "JZ-CHAR", "JZ-INNER", "JZ-HWS"];
function buildWrapSet(options) {
const s = new Set(JZ_WRAP_TAGS);
for (const name of options.finder.styleInlines) {
s.add(name);
}
return s;
}
function rectOf(n, side) {
if (n.nodeType === 3 && n.data.length > 0) {
const t = n;
const doc2 = t.ownerDocument;
if (!doc2) {
return null;
}
const range = doc2.createRange();
const at = side === "prev" ? previousVariationSequenceStart(t.data, t.data.length) : 0;
const length = variationSequenceAtUtf16(t.data, at).length;
range.setStart(t, at);
range.setEnd(t, at + length);
const rects = range.getClientRects();
if (rects.length) {
return rects[side === "prev" ? rects.length - 1 : 0];
}
} else if (n.nodeType === 1) {
const rects = n.getClientRects();
if (rects.length) {
return rects[side === "prev" ? rects.length - 1 : 0];
}
}
return null;
}
function edgeRect(start, side, from, wrapSet) {
let n = start;
let parentRef = from.parentNode;
for (; ; ) {
while (n) {
const r = rectOf(n, side);
if (r) {
return r;
}
n = side === "prev" ? n.previousSibling : n.nextSibling;
}
if (!parentRef || parentRef.nodeType !== 1) {
return null;
}
if (!wrapSet.has(parentRef.nodeName)) {
return null;
}
n = side === "prev" ? parentRef.previousSibling : parentRef.nextSibling;
parentRef = parentRef.parentNode;
}
}
function firstRect(el) {
const rects = el.getClientRects();
return rects.length ? rects[0] : null;
}
function lowerLine(a, b) {
return b.top >= a.bottom - EPS;
}
function higherLine(a, b) {
return b.bottom <= a.top + EPS;
}
function lastRect(el) {
const rects = el.getClientRects();
return rects.length ? rects[rects.length - 1] : null;
}
function clearEdge(ctx) {
for (const cls of ALL_EDGE) {
ctx.root.querySelectorAll("jz-char." + cls).forEach((e) => {
if (ctx.ownedElements.has(e)) {
ctx.setOwnedClass(e, cls, false);
}
});
}
}
function relayout(ctx) {
clearEdge(ctx);
const wrapSet = buildWrapSet(ctx.options);
const chars = Array.from(ctx.root.querySelectorAll("jz-char")).filter(
(c) => ctx.ownedElements.has(c) && ctx.finder.inScope(c) && !ctx.finder.isAvoided(c)
);
const decisions = [];
for (const el of chars) {
if (!ctx.finder.levelAllows(el, "paragraph")) {
continue;
}
if (!ctx.finder.featureEnabledFor(el, "jiya")) {
continue;
}
const bd = bdClassOf(el);
if (!bd) {
continue;
}
const isHead = HEAD_CLASSES.has(bd);
const isTail = TAIL_CLASSES.has(bd);
if (!isHead && !isTail) {
continue;
}
const r = firstRect(el);
if (!r) {
continue;
}
if (isHead) {
const prev = edgeRect(el.previousSibling, "prev", el, wrapSet);
if (!prev || higherLine(r, prev)) {
decisions.push({ el });
}
} else {
const next = edgeRect(el.nextSibling, "next", el, wrapSet);
if (!next || lowerLine(r, next)) {
decisions.push({ el });
}
}
}
for (const d of decisions) {
ctx.setOwnedClass(d.el, LE, true);
}
}
var lineEdgePass = {
name: "jiyaLineEdge",
kind: "standalone",
order: 90,
level: "paragraph",
enabled: (o) => o.features.jiya,
render(ctx) {
if (!hasLayout()) {
return;
}
installLayout(ctx.root, ctx.ownerToken, "lineEdge", () => {
relayout(ctx);
});
},
revert(ctx) {
uninstallLayout(ctx.root, ctx.ownerToken, "lineEdge");
clearEdge(ctx);
}
};
var TRIM = "jz-hws-trim";
function skippableAlign(g, cache) {
const key = g.parentElement;
if (!key) {
return false;
}
const cached = cache.get(key);
if (cached !== void 0) {
return cached;
}
let result = false;
const view = g.ownerDocument ? g.ownerDocument.defaultView : null;
if (view) {
const cs = view.getComputedStyle(g);
const ltr = cs.direction !== "rtl";
result = cs.textAlign === "left" || cs.textAlign === "start" && ltr;
}
cache.set(key, result);
return result;
}
function trimGaps(ctx, wrapSet) {
const alignCache = /* @__PURE__ */ new Map();
const gaps = Array.from(ctx.root.querySelectorAll("jz-hws")).filter(
(g) => ctx.ownedElements.has(g) && ctx.finder.inScope(g) && !ctx.finder.isAvoided(g) && ctx.finder.levelAllows(g, "paragraph") && ctx.finder.featureEnabledFor(g, "spacing") && !skippableAlign(g, alignCache)
);
if (!gaps.length) {
return;
}
gaps.forEach((g) => ctx.setOwnedClass(g, TRIM, true));
for (const g of gaps) {
const r = lastRect(g);
const next = r ? edgeRect(g.nextSibling, "next", g, wrapSet) : null;
const atLineEnd = !r || !next || next.top > r.top + EPS;
if (!atLineEnd) {
ctx.setOwnedClass(g, TRIM, false);
}
}
}
var gapTrimPass = {
name: "spacingGapTrim",
kind: "standalone",
order: 92,
level: "paragraph",
enabled: (o) => o.features.spacing,
render(ctx) {
if (!hasLayout()) {
return;
}
const wrapSet = buildWrapSet(ctx.options);
installLayout(ctx.root, ctx.ownerToken, "gapTrim", () => {
trimGaps(ctx, wrapSet);
});
},
revert(ctx) {
uninstallLayout(ctx.root, ctx.ownerToken, "gapTrim");
ctx.root.querySelectorAll("jz-hws." + TRIM).forEach((e) => {
if (ctx.ownedElements.has(e)) {
ctx.setOwnedClass(e, TRIM, false);
}
});
}
};
// src/typeset/longword.ts
var PASS3 = "longWord";
function inLangSpan(node) {
const p = node.parentNode;
if (!p || p.nodeType !== 1) {
return false;
}
const el = p;
if (el.nodeName !== "SPAN") {
return false;
}
return el.hasAttribute("lang");
}
var longWordPass = {
name: PASS3,
kind: "charify",
order: 60,
level: "paragraph",
enabled: (o) => o.features.longWord,
collect(node, ctx) {
if (inLangSpan(node)) {
return [];
}
if (!ctx.finder.featureEnabledFor(node, PASS3)) {
return [];
}
const cfg = ctx.options.longWord;
const re = new RegExp("[A-Za-z]{" + cfg.minLength + ",}", "g");
const data = node.data;
const out = [];
let m;
while ((m = re.exec(data)) !== null) {
const start = m.index;
const end = m.index + m[0].length;
out.push({
start,
end,
pass: PASS3,
build: (text) => {
const span = node.ownerDocument.createElement("span");
span.setAttribute("lang", cfg.lang);
span.setAttribute("data-jz", PASS3);
span.setAttribute("data-jz-kind", "wrap");
span.textContent = text;
ctx.ownedElements.add(span);
return span;
}
});
}
return out;
},
revert(ctx) {
revertPass(PASS3, ctx.root, ctx.ownedElements);
}
};
// src/typeset/orphan.ts
var PASS4 = "orphan";
function hasOrphan(block) {
for (let c = block.firstChild; c; c = c.nextSibling) {
if (c.nodeType === 1 && c.nodeName === "JZ-ORPHAN") {
return true;
}
}
return false;
}
function collectSubst(block, finder, blockSet, anyCjk, biaodian) {
const out = [];
const visit = (node, directChild) => {
if (node.nodeType === 3) {
const t = node;
const d = t.data;
for (let i = 0; i < d.length; ) {
const ch = variationSequenceAtUtf16(d, i);
if (anyCjk.test(ch) && !classifyBiaodian(ch, biaodian)) {
out.push({ directChild, textNode: t, offset: i });
}
i += ch.length;
}
return;
}
if (node.nodeType !== 1) {
return;
}
const el = node;
if (finder.avoidsSelf(el)) {
return;
}
if (blockSet.has(el.nodeName) || finder.pillMatches(el)) {
return;
}
for (let c = el.firstChild; c; c = c.nextSibling) {
visit(c, directChild);
}
};
for (let dc = block.firstChild; dc; dc = dc.nextSibling) {
visit(dc, dc);
}
return out;
}
function processBlock(block, finder, blockSet, anyCjk, biaodian, chars, ownedElements) {
if (hasOrphan(block)) {
return;
}
const subst = collectSubst(block, finder, blockSet, anyCjk, biaodian);
if (subst.length < chars) {
return;
}
const boundary = subst[subst.length - chars];
let startChild = boundary.directChild;
if (boundary.textNode && boundary.textNode === startChild) {
if (boundary.offset > 0) {
startChild = boundary.textNode.splitText(boundary.offset);
}
} else if (boundary.textNode && startChild.nodeType === 1) {
const clone = splitElementAt(
startChild,
boundary.textNode,
boundary.offset,
PASS4,
finder,
ownedElements
);
if (clone) {
startChild = clone;
}
}
const orphan = createJz(ownedElements, "jz-orphan", PASS4, "wrap");
block.insertBefore(orphan, startChild);
let n = startChild;
while (n) {
const next = n.nextSibling;
orphan.appendChild(n);
n = next;
}
}
var orphanPass = {
name: PASS4,
kind: "standalone",
order: 70,
level: "paragraph",
enabled: (o) => o.features.orphan,
render(ctx) {
const { finder, options } = ctx;
const blockSet = options.finder.blockTags;
const anyCjk = new RegExp("[" + options.ruleset.cjk + "]", "u");
const biaodian = options.ruleset.biaodian;
const chars = options.orphan.chars;
const sel = Array.from(blockSet).map((t) => t.toLowerCase()).join(",");
const nonLeaf = /* @__PURE__ */ new Set();
if (sel) {
ctx.root.querySelectorAll(sel).forEach((b) => {
let p = b.parentElement;
while (p) {
if (blockSet.has(p.nodeName)) {
nonLeaf.add(p);
break;
}
p = p.parentElement;
}
});
}
finder.eachBlock(ctx.root, (block) => {
if (!finder.levelAllows(block, "paragraph")) {
return;
}
if (!finder.featureEnabledFor(block, PASS4)) {
return;
}
if (finder.isAvoided(block)) {
return;
}
if (nonLeaf.has(block)) {
return;
}
processBlock(
block,
finder,
blockSet,
anyCjk,
biaodian,
chars,
ctx.ownedElements
);
});
},
revert(ctx) {
revertPass(PASS4, ctx.root, ctx.ownedElements);
}
};
// src/typeset/spacing.ts
var PASS5 = "spacing";
var MARK = "\uE000";
var PH_OPEN = "\uE001";
var PH_CLOSE = "\uE002";
var OPS_HYPHEN = "\\+\\*=&\\-";
var OPS_GRADE = "\\+\\-\\*";
var QUOTES_CLS = '`"\u05F4';
var LBR_BASIC = "\\(\\[\\{";
var RBR_BASIC = "\\)\\]\\}";
var LBR_EXT = "\\(\\[\\{<>";
var RBR_EXT = "\\)\\]\\}<>";
var DEL = String.fromCharCode(57347);
function makeRules(rs) {
const CJK2 = rs.cjk;
const ANS_AFTER = rs.ansAfter;
const ANS_BEFORE = rs.ansBefore;
const CJK_ATOM = "(?:[" + CJK2 + "](?:[" + VARIATION_SELECTORS + "])?)";
const CJK_OR_ALNUM = "(?:" + CJK_ATOM + "|[" + ALNUM + "])";
return {
anyCjk: new RegExp(CJK_ATOM, "u"),
pillNeighbor: new RegExp("^(?:[" + ALNUM + "]|" + CJK_ATOM + ")$", "u"),
STRIP_CJK_SPACE_ANS: new RegExp("(" + CJK_ATOM + ") ([" + ANS_AFTER + "])", "gu"),
STRIP_ANS_SPACE_CJK: new RegExp("([" + ANS_BEFORE + "]) (" + CJK_ATOM + ")", "gu"),
STRIP_CJK_SPACE_CJK: new RegExp("(" + CJK_ATOM + ") (?=" + CJK_ATOM + ")", "gu"),
DOTS_CJK: new RegExp("([\\.]{2,})(" + CJK_ATOM + ")", "gu"),
CJK_PUNCTUATION: new RegExp("(" + CJK_ATOM + ")([!;,\\?:]+)(?=" + CJK_OR_ALNUM + ")", "gu"),
AN_PUNCTUATION_CJK: new RegExp("([" + ALNUM + "])([!;,\\?]+)(" + CJK_ATOM + ")", "gu"),
CJK_TILDE: new RegExp("(" + CJK_ATOM + ")(~+)(?!=)(?=" + CJK_OR_ALNUM + ")", "gu"),
CJK_TILDE_EQUALS: new RegExp("(" + CJK_ATOM + ")(~=)", "gu"),
CJK_PERIOD: new RegExp("(" + CJK_ATOM + ")(\\.)(?![" + ALNUM + "\\./])(?=" + CJK_OR_ALNUM + ")", "gu"),
AN_PERIOD_CJK: new RegExp("([" + ALNUM + "])(\\.)(" + CJK_ATOM + ")", "gu"),
AN_COLON_CJK: new RegExp("([" + ALNUM + "])(:)(" + CJK_ATOM + ")", "gu"),
CJK_QUOTE: new RegExp("(" + CJK_ATOM + ")([" + QUOTES_CLS + "])", "gu"),
QUOTE_CJK: new RegExp("([" + QUOTES_CLS + "])(" + CJK_ATOM + ")", "gu"),
CJK_QUOTE_AN: new RegExp("(" + CJK_ATOM + ')(")([' + ALNUM + "])", "gu"),
CJK_HASH: new RegExp("(" + CJK_ATOM + ")(#([^ ]))", "gu"),
HASH_CJK: new RegExp("(([^ ])#)(" + CJK_ATOM + ")", "gu"),
SINGLE_LETTER_GRADE: new RegExp("\\b([" + ALPHA + "])([" + OPS_GRADE + "])(" + CJK_ATOM + ")", "gu"),
CJK_OPERATOR_ANS: new RegExp("(" + CJK_ATOM + ")([" + OPS_HYPHEN + "])([" + ALNUM + "])", "gu"),
ANS_OPERATOR_CJK: new RegExp("([" + ALNUM + "])([" + OPS_HYPHEN + "])(" + CJK_ATOM + ")", "gu"),
CJK_LESS_THAN: new RegExp("(" + CJK_ATOM + ")(<)([" + ALNUM + "])", "gu"),
LESS_THAN_CJK: new RegExp("([" + ALNUM + "])(<)(" + CJK_ATOM + ")", "gu"),
CJK_GREATER_THAN: new RegExp("(" + CJK_ATOM + ")(>)([" + ALNUM + "])", "gu"),
GREATER_THAN_CJK: new RegExp("([" + ALNUM + "])(>)(" + CJK_ATOM + ")", "gu"),
CJK_LEFT_BRACKET: new RegExp("(" + CJK_ATOM + ")([" + LBR_EXT + "])", "gu"),
RIGHT_BRACKET_CJK: new RegExp("([" + RBR_EXT + "])(" + CJK_ATOM + ")", "gu"),
// 英數字串段前之字元一併匹配,才能在不用 lookbehind 下排除 `.foo(`。
// 括號只作 lookahead,避免 `a(b(` 之前一次匹配吞掉 `(` 而漏掉第二處。
AN_LEFT_BRACKET: new RegExp(
"(^|[^." + ALNUM + "])([" + ALNUM + "]+)(?=[" + LBR_BASIC + "])",
"g"
),
RIGHT_BRACKET_AN: new RegExp("([" + RBR_BASIC + "])([" + ALNUM + "])", "g"),
CJK_ANS: new RegExp("(" + CJK_ATOM + ")([" + ANS_AFTER + "])", "gu"),
ANS_CJK: new RegExp("([" + ANS_BEFORE + "])(" + CJK_ATOM + ")", "gu"),
S_A: new RegExp("(%)([" + ALPHA + "])", "g")
};
}
var COMPOUND_WORD = /\b(?:[A-Za-z0-9]*[a-z][A-Za-z0-9]*-[A-Za-z0-9]+|[A-Za-z0-9]+-[A-Za-z0-9]*[a-z][A-Za-z0-9]*|[A-Za-z]+-[0-9]+|[A-Za-z]+[0-9]+-[A-Za-z0-9]+)(?:-[A-Za-z0-9]+)*\b/g;
var PH_PATTERN = new RegExp(PH_OPEN + "(\\d+)" + PH_CLOSE, "g");
var ASTRAL_CJK_SHADOW = "\u6F22";
function shadowAstralCjkAtoms(atoms, R) {
const shadowed = new Array(atoms.length);
for (let i = 0; i < atoms.length; i += 1) {
const atom = atoms[i];
const base = atom.codePointAt(0);
const match = base !== void 0 && base > 65535 ? R.anyCjk.exec(atom) : null;
shadowed[i] = match?.index === 0 ? ASTRAL_CJK_SHADOW : atom;
}
return shadowed.join("");
}
function panguSpace(text, R) {
if (splitVariationSequences(text).length <= 1 || !R.anyCjk.test(text)) {
return text;
}
let s = text;
s = s.replace(R.STRIP_CJK_SPACE_ANS, "$1$2");
s = s.replace(R.STRIP_ANS_SPACE_CJK, "$1$2");
s = s.replace(R.STRIP_CJK_SPACE_CJK, "$1" + DEL);
s = s.replace(R.DOTS_CJK, "$1" + MARK + "$2");
s = s.replace(R.CJK_PUNCTUATION, "$1$2" + MARK);
s = s.replace(R.AN_PUNCTUATION_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_TILDE, "$1$2" + MARK);
s = s.replace(R.CJK_TILDE_EQUALS, "$1" + MARK + "$2" + MARK);
s = s.replace(R.CJK_PERIOD, "$1$2" + MARK);
s = s.replace(R.AN_PERIOD_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.AN_COLON_CJK, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_QUOTE, "$1" + MARK + "$2");
s = s.replace(R.QUOTE_CJK, "$1" + MARK + "$2");
s = s.replace(R.CJK_QUOTE_AN, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_HASH, "$1" + MARK + "$2");
s = s.replace(R.HASH_CJK, "$1" + MARK + "$3");
const compounds = [];
s = s.replace(COMPOUND_WORD, (m) => {
compounds.push(m);
return PH_OPEN + (compounds.length - 1) + PH_CLOSE;
});
s = s.replace(R.SINGLE_LETTER_GRADE, "$1$2" + MARK + "$3");
s = s.replace(R.CJK_OPERATOR_ANS, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.ANS_OPERATOR_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.CJK_LESS_THAN, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.LESS_THAN_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.CJK_GREATER_THAN, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(R.GREATER_THAN_CJK, "$1" + MARK + "$2" + MARK + "$3");
s = s.replace(PH_PATTERN, (_m, idx) => compounds[Number(idx)] || "");
s = s.replace(R.CJK_LEFT_BRACKET, "$1" + MARK + "$2");
s = s.replace(R.RIGHT_BRACKET_CJK, "$1" + MARK + "$2");
s = s.replace(R.AN_LEFT_BRACKET, "$1$2" + MARK);
s = s.replace(R.RIGHT_BRACKET_AN, "$1" + MARK + "$2");
s = s.replace(R.CJK_ANS, "$1" + MARK + "$2");
s = s.replace(R.ANS_CJK, "$1" + MARK + "$2");
s = s.replace(R.S_A, "$1" + MARK + "$2");
return s;
}
var MOVED_PARTS = /* @__PURE__ */ new WeakMap();
var MOVED_ANCHOR = "jz-spacing-anchor";
var MOVED_GAP_ANCHOR = "jz-spacing-gap-anchor";
var CROSS_NODE_ATTR = "data-jz-cross-node-atom";
function wrapNodeInGap(node, ownedElements) {
const parent = node.parentNode;
if (!parent || parent.nodeName === "JZ-HWS") {
return;
}
const gap = createJz(ownedElements, "jz-hws", PASS5, "wrap");
parent.insertBefore(gap, node);
gap.appendChild(node);
}
function isolateTextSpan(part) {
let tn = part.textNode;
if (!tn.parentNode) {
return null;
}
if (part.start > 0) {
tn = tn.splitText(part.start);
}
const length = part.end - part.start;
if (tn.data.length > length) {
tn.splitText(length);
}
return tn;
}
function wrapSingleSpanInGap(part, ownedElements) {
const containingGap = part.textNode.parentElement?.closest("jz-hws");
if (containingGap) {
return ownedElements.has(containingGap) && containingGap.getAttribute("data-jz") === PASS5 && containingGap.getAttribute("data-jz-kind") === "wrap" ? containingGap : null;
}
const tn = isolateTextSpan(part);
if (!tn) {
return null;
}
const parent = tn.parentNode;
if (!parent) {
return null;
}
const gap = createJz(ownedElements, "jz-hws", PASS5, "wrap");
try {
parent.insertBefore(gap, tn);
gap.appendChild(tn);
return gap;
} catch (error) {
if (tn.parentNode === gap) {
parent.insertBefore(tn, gap);
}
if (gap.parentNode) {
gap.parentNode.removeChild(gap);
}
ownedElements.delete(gap);
parent.normalize?.();
throw error;
}
}
function isOwnedCrossGap(gap, ownedElements) {
return ownedElements.has(gap) && gap.getAttribute("data-jz") === PASS5 && gap.getAttribute("data-jz-kind") === "wrap" && gap.hasAttribute(CROSS_NODE_ATTR);
}
function trustedInsertionPoint(record) {
if (record.anchor.parentNode === record.originalParent) {
return record.anchor;
}
if (record.originalNextSibling?.parentNode === record.originalParent) {
return record.originalNextSibling;
}
if (record.originalNextSibling !== null) {
return void 0;
}
if (record.originalPreviousSibling?.parentNode === record.originalParent) {
return record.originalPreviousSibling === record.originalParent.lastChild ? null : void 0;
}
if (record.originalPreviousSibling === null && record.originalNextSibling === null && record.anchor.parentNode !== null && record.anchor.parentNode !== record.originalParent && !record.originalParent.hasChildNodes()) {
return null;
}
return void 0;
}
function restoreMovedText(record) {
if (record.textNode.parentNode === record.originalParent) {
return true;
}
if (record.textNode.parentNode !== record.gap) {
return false;
}
const before = trustedInsertionPoint(record);
if (before === void 0) {
return false;
}
record.originalParent.insertBefore(record.textNode, before);
return true;
}
function movePartIntoGap(part, gap, block, scopeRoot) {
const textNode = isolateTextSpan(part);
const parent = textNode?.parentNode;
const gapParent = gap.parentNode;
if (!textNode || !parent || !gapParent) {
return null;
}
const anchor = textNode.ownerDocument.createComment(MOVED_ANCHOR);
const gapAnchor = textNode.ownerDocument.createComment(MOVED_GAP_ANCHOR);
const record = {
anchor,
gapAnchor,
gap,
textNode,
originalParent: parent,
originalPreviousSibling: textNode.previousSibling,
originalNextSibling: textNode.nextSibling,
gapOriginalParent: gapParent,
gapOriginalPreviousSibling: gap.previousSibling,
gapOriginalNextSibling: gap.nextSibling,
block,
scopeRoot,
scopeAncestors: (() => {
const ancestors = [];
for (let parent2 = scopeRoot.parentElement; parent2; parent2 = parent2.parentElement) {
ancestors.push(parent2);
}
return ancestors;
})()
};
try {
parent.insertBefore(anchor, textNode);
gap.appendChild(gapAnchor);
gap.appendChild(textNode);
return record;
} catch (error) {
restoreMovedText(record);
if (anchor.parentNode) {
anchor.parentNode.removeChild(anchor);
}
if (gapAnchor.parentNode) {
gapAnchor.parentNode.removeChild(gapAnchor);
}
parent.normalize?.();
throw error;
}
}
function wrapGapLeft(span, ownedElements, ownerToken, block, scopeRoot) {
const containingGaps = /* @__PURE__ */ new Set();
for (const part of span.parts) {
const containing = part.textNode.parentElement?.closest("jz-hws");
if (containing) {
containingGaps.add(containing);
}
}
if (containingGaps.size > 0) {
if (containingGaps.size === 1) {
const existing = Array.from(containingGaps)[0];
if (isOwnedCrossGap(existing, ownedElements) && span.parts.every((part) => existing.contains(part.textNode))) {
return;
}
}
return;
}
const gap = wrapSingleSpanInGap(span.parts[0], ownedElements);
if (!gap) {
return;
}
if (span.parts.length === 1) {
return;
}
const moved = [];
try {
for (let i = 1; i < span.parts.length; i += 1) {
const record = movePartIntoGap(span.parts[i], gap, block, scopeRoot);
if (!record) {
throw new Error("juzhen: \u8DE8\u7BC0\u9EDE\u5B57\u5143\u642C\u79FB\u5931\u6557\u3002");
}
moved.push(record);
}
gap.setAttribute(CROSS_NODE_ATTR, "");
let records = MOVED_PARTS.get(ownerToken);
if (!records) {
records = /* @__PURE__ */ new Set();
MOVED_PARTS.set(ownerToken, records);
}
for (const record of moved) {
records.add(record);
}
} catch (error) {
for (let i = moved.length - 1; i >= 0; i -= 1) {
const record = moved[i];
restoreMovedText(record);
if (record.anchor.parentNode) {
record.anchor.parentNode.removeChild(record.anchor);
}
if (record.gapAnchor.parentNode) {
record.gapAnchor.parentNode.removeChild(record.gapAnchor);
}
record.originalParent.normalize?.();
}
gap.removeAttribute(CROSS_NODE_ATTR);
const gapParent = gap.parentNode;
if (gapParent) {
while (gap.firstChild) {
gapParent.insertBefore(gap.firstChild, gap);
}
gapParent.removeChild(gap);
gapParent.normalize?.();
}
ownedElements.delete(gap);
throw error;
}
}
function recordWithinRoot(record, root) {
const within = (node) => node === root || root.contains(node);
const detached = !record.anchor.isConnected && !record.gap.isConnected && !record.gapAnchor.isConnected && !record.originalParent.isConnected;
return root === record.scopeRoot || root.contains(record.scopeRoot) || record.scopeAncestors.some((ancestor) => root === ancestor || root.contains(ancestor)) || within(record.gap) || within(record.anchor) || within(record.originalParent) || within(record.block) || detached && root === root.ownerDocument.body;
}
function placeGapAtOriginalParent(record) {
if (record.gap.parentNode === record.gapOriginalParent) {
return true;
}
let before = null;
if (record.gapOriginalNextSibling?.parentNode === record.gapOriginalParent) {
before = record.gapOriginalNextSibling;
} else if (record.gapOriginalNextSibling !== null) {
return false;
} else if (record.gapOriginalPreviousSibling?.parentNode === record.gapOriginalParent) {
if (record.gapOriginalPreviousSibling !== record.gapOriginalParent.lastChild) {
return false;
}
before = null;
} else if (record.gapOriginalParent.lastChild !== record.gapOriginalPreviousSibling || record.gapOriginalPreviousSibling === null && record.gapOriginalNextSibling === null && record.gapOriginalParent.hasChildNodes()) {
return false;
}
record.gapOriginalParent.insertBefore(record.gap, before);
return true;
}
function unwrapCrossGap(record, ownedElements, touched) {
const gap = record.gap;
if (!isOwnedCrossGap(gap, ownedElements)) {
return;
}
if (!placeGapAtOriginalParent(record)) {
if (!gap.isConnected) {
ownedElements.delete(gap);
}
return;
}
const parent = gap.parentNode;
if (!parent) {
ownedElements.delete(gap);
return;
}
while (gap.firstChild) {
parent.insertBefore(gap.firstChild, gap);
}
parent.removeChild(gap);
ownedElements.delete(gap);
touched.add(parent);
}
function restoreMovedParts(ctx) {
const touched = /* @__PURE__ */ new Set();
const records = MOVED_PARTS.get(ctx.ownerToken);
if (!records) {
return touched;
}
const gaps = /* @__PURE__ */ new Map();
for (const record of Array.from(records)) {
if (!recordWithinRoot(record, ctx.root)) {
continue;
}
const restored = restoreMovedText(record);
if (!restored && record.textNode.parentNode === record.gap) {
record.gap.removeChild(record.textNode);
}
if (restored) {
touched.add(record.originalParent);
}
if (record.anchor.parentNode) {
record.anchor.parentNode.removeChild(record.anchor);
}
if (record.gapAnchor.parentNode) {
record.gapAnchor.parentNode.removeChild(record.gapAnchor);
}
gaps.set(record.gap, record);
records.delete(record);
}
for (const record of gaps.values()) {
unwrapCrossGap(record, ctx.ownedElements, touched);
}
if (records.size === 0) {
MOVED_PARTS.delete(ctx.ownerToken);
}
return touched;
}
function deleteSpan(span) {
for (let i = span.parts.length - 1; i >= 0; i -= 1) {
const part = span.parts[i];
part.textNode.data = part.textNode.data.slice(0, part.start) + part.textNode.data.slice(part.end);
}
}
function processBlock2(block, scopeRoot, finder, R, ownedElements, ownerToken) {
const runs = finder.collectRuns(block);
for (const run of runs) {
const { map } = run;
const originalChars = map.map((span) => span.text);
const ruleText = shadowAstralCjkAtoms(originalChars, R);
const spaced = panguSpace(ruleText, R);
if (spaced === ruleText) {
continue;
}
const actions = [];
let origIdx = 0;
for (const sc of splitVariationSequences(spaced)) {
if (sc === DEL) {
actions.push({ before: void 0, space: map[origIdx] });
origIdx += 1;
continue;
}
if (sc === MARK) {
const oc = origIdx < originalChars.length ? originalChars[origIdx] : null;
if (oc === " ") {
actions.push({ before: map[origIdx - 1], space: map[origIdx] });
origIdx += 1;
} else {
actions.push({ before: map[origIdx - 1], space: null });
}
continue;
}
origIdx += 1;
}
for (let i = actions.length - 1; i >= 0; i -= 1) {
const act = actions[i];
if (act.space) {
deleteSpan(act.space);
}
if (act.before) {
wrapGapLeft(act.before, ownedElements, ownerToken, block, scopeRoot);
}
}
}
}
function processPills(root, finder, boundarySelector, pillNeighbor, ownedElements, ownerToken) {
root.querySelectorAll(boundarySelector).forEach((pill) => {
if (finder.isAvoided(pill) || finder.insidePill(pill) || !finder.inScope(pill)) {
return;
}
if (!finder.featureEnabledFor(pill, PASS5)) {
return;
}
if (!pill.parentNode) {
return;
}
const prev = finder.adjacentLogicalChar(pill, -1);
if (prev) {
handlePillSide(prev, pill, -1, pillNeighbor, ownedElements, ownerToken, root);
}
const next = finder.adjacentLogicalChar(pill, 1);
if (next) {
handlePillSide(next, pill, 1, pillNeighbor, ownedElements, ownerToken, root);
}
});
}
function handlePillSide(adj, pill, dir, pillNeighbor, ownedElements, ownerToken, scopeRoot) {
if (dir === -1) {
if (pillNeighbor.test(adj.ch)) {
wrapGapLeft({
text: adj.ch,
parts: [{
textNode: adj.textNode,
start: adj.offset,
end: adj.offset + adj.ch.length
}]
}, ownedElements, ownerToken, pill.parentElement ?? scopeRoot, scopeRoot);
return;
}
if (adj.ch === " " && adj.offset >= 1) {
const tn = adj.textNode;
const beforeOffset = previousVariationSequenceStart(tn.data, adj.offset);
const bc = variationSequenceAtUtf16(tn.data, beforeOffset);
if (bc !== " " && pillNeighbor.test(bc)) {
tn.data = tn.data.slice(0, adj.offset) + tn.data.slice(adj.offset + 1);
wrapGapLeft({
text: bc,
parts: [{ textNode: tn, start: beforeOffset, end: beforeOffset + bc.length }]
}, ownedElements, ownerToken, pill.parentElement ?? scopeRoot, scopeRoot);
}
}
} else {
if (pillNeighbor.test(adj.ch)) {
wrapNodeInGap(pill, ownedElements);
return;
}
if (adj.ch === " " && adj.textNode.data.length > 1) {
const ac = variationSequenceAtUtf16(adj.textNode.data, 1);
if (ac !== " " && pillNeighbor.test(ac)) {
adj.textNode.data = adj.textNode.data.slice(1);
wrapNodeInGap(pill, ownedElements);
}
}
}
}
var spacingPass = {
name: PASS5,
kind: "standalone",
order: 50,
level: "text",
enabled: (o) => o.features.spacing,
render(ctx) {
const { finder, options } = ctx;
const R = makeRules(options.ruleset);
ctx.finder.eachBlock(ctx.root, (block) => {
if (!finder.featureEnabledFor(block, PASS5)) {
return;
}
processBlock2(block, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken);
});
if (!options.finder.blockTags.has(ctx.root.nodeName) && finder.featureEnabledFor(ctx.root, PASS5) && finder.inScope(ctx.root) && !finder.isAvoided(ctx.root)) {
processBlock2(ctx.root, ctx.root, finder, R, ctx.ownedElements, ctx.ownerToken);
}
const boundarySelector = [options.finder.pillSelector, options.finder.isolateSelector].filter(Boolean).join(", ");
if (boundarySelector) {
processPills(
ctx.root,
finder,
boundarySelector,
R.pillNeighbor,
ctx.ownedElements,
ctx.ownerToken
);
}
},
revert(ctx) {
const touched = restoreMovedParts(ctx);
revertPass(PASS5, ctx.root, ctx.ownedElements);
for (const parent of touched) {
parent.normalize?.();
}
}
};
// src/index.ts
var DEFAULT_STYLE_INLINES = [
"STRONG",
"EM",
"A",
"B",
"I",
"U",
"S",
"MARK",
"INS",
"DEL",
"SUB",
"SUP",
"SMALL",
"ABBR",
"CITE",
"DFN",
"Q",
"SPAN",
"VAR",
"SAMP",
"TIME",
"BDO",
"BDI",
"RUBY",
"RB",
"RT",
"RP",
"WBR"
];
var DEFAULT_BLOCK_TAGS = [
"BODY",
"P",
"DIV",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"LI",
"DT",
"DD",
"BLOCKQUOTE",
"FIGURE",
"FIGCAPTION",
"TD",
"TH",
"CAPTION",
"SUMMARY",
"DETAILS",
"HEADER",
"FOOTER",
"SECTION",
"ARTICLE",
"ASIDE",
"NAV",
"MAIN",
"ADDRESS",
"PRE",
"UL",
"OL",
"DL",
"TABLE",
"TR",
"THEAD",
"TBODY",
"TFOOT",
"FORM",
"FIELDSET",
"LEGEND",
"LABEL",
"BR",
"HR"
];
var DEFAULT_SKIP_TAGS = [
"PRE",
"SAMP",
"TT",
"VAR",
"SCRIPT",
"STYLE",
"TEXTAREA",
"INPUT"
];
var PASSES = [
jiyaPass,
jiyaAdjacencyPass,
longWordPass,
spacingPass,
jinzePass,
orphanPass,
lineEdgePass,
gapTrimPass,
levelMarkPass
];
function feature(v, dflt) {
return v === void 0 ? dflt : !!v;
}
function resolveLongWord(v) {
if (v === false) {
return { minLength: 6, lang: "en" };
}
if (v === void 0 || v === true || v === null) {
return { minLength: 6, lang: "en" };
}
const minLength = typeof v.minLength === "number" && v.minLength >= 2 ? v.minLength : 6;
const lang = typeof v.lang === "string" && v.lang ? v.lang : "en";
return { minLength, lang };
}
function resolveOrphan(v) {
if (v && typeof v === "object") {
const chars = typeof v.chars === "number" && v.chars >= 1 ? Math.floor(v.chars) : 2;
return { chars };
}
return { chars: 2 };
}
function resolveJiya(v) {
if (v && typeof v === "object") {
return {
halfWidth: v.halfWidth === "margin" ? "margin" : "halt",
bias: v.bias || {}
};
}
return { halfWidth: "halt", bias: {} };
}
function normalizeOptions(opts = {}) {
const langOpt = opts.lang || {};
const scope = opts.scope || {};
return {
locale: {
default: langOpt.default || "zh-Hant",
...langOpt.style ? { style: langOpt.style } : {},
policy: langOpt.policy || {},
stylePolicy: Array.isArray(langOpt.stylePolicy) ? langOpt.stylePolicy.slice() : []
},
ruleset: resolveRuleset(opts.charClass),
justifyAtoms: opts.justifyAtoms === false ? false : true,
levelText: opts.level && opts.level.text || null,
scope: {
root: scope.root || null,
include: scope.include || null,
avoid: scope.avoid || null
},
features: {
spacing: feature(opts.spacing, true),
// feature(v,dflt) 對 v===false 即回 false,故無需 ===false 特判(與 jiya/jinze 同構)。
longWord: feature(opts.longWord, true),
jiya: feature(opts.jiya, true),
jinze: feature(opts.jinze, true),
orphan: feature(opts.orphan, true),
biaodian: feature(opts.biaodian, false),
emphasis: feature(opts.emphasis, false),
ruby: feature(opts.ruby, false)
},
longWord: resolveLongWord(opts.longWord),
orphan: resolveOrphan(opts.orphan),
// 行端僅做半形擠壓(lineedge:行首開類收左、行尾閉類收右)。**標點懸掛已移除**
// (§6.5.6):純負 margin 機制為機制級死路(寬度守恆矛盾,CSS 接觸不到斷行器);
// opts.hanging 仍被接受以保 API 相容,但無任何效果。
jiyaConfig: resolveJiya(opts.jiya),
blocks: Array.isArray(opts.blocks) ? opts.blocks.slice() : [],
finder: {
styleInlines: new Set(opts.styleInlines || DEFAULT_STYLE_INLINES),
blockTags: new Set(opts.blockTags || DEFAULT_BLOCK_TAGS),
skipTags: new Set(opts.skipTags || DEFAULT_SKIP_TAGS),
pillSelector: opts.pillSelector || "code, kbd",
// 內外隔離(§4.1 三分類):預設 <mark>(瀏覽器預設帶背景,切分有可見
// 接縫、邊界需留隙)。顯式判 undefined,使 "" 可停用。
isolateSelector: opts.isolateSelector !== void 0 ? opts.isolateSelector : "mark",
skipAttribute: opts.skipAttribute || null,
isSkipped: typeof opts.isSkipped === "function" ? opts.isSkipped : null
}
};
}
function resolveRoot(arg, options) {
const pick = arg !== void 0 ? arg : options.scope.root;
if (pick && typeof pick !== "string") {
return pick;
}
if (typeof pick === "string" && typeof document !== "undefined") {
return document.querySelector(pick);
}
return typeof document !== "undefined" ? document.body : null;
}
var OBSERVE_ATTRS = [
"class",
"lang",
"id",
"contenteditable",
"data-jz-skip",
"data-jz-level",
"data-jz-style",
"data-juzhen",
"data-juzhen-off"
];
function createJuzhen(opts = {}) {
const options = normalizeOptions(opts);
const finder = new Finder(options);
const ownedElements = /* @__PURE__ */ new WeakSet();
const ownerToken = {};
const renderedRoots = /* @__PURE__ */ new Set();
function makeCtx(root) {
return { root, options, finder, ownedElements, ownerToken, setOwnedClass };
}
function renderImpl(el) {
renderedRoots.add(el);
runPasses(PASSES, makeCtx(el));
if (options.features.jiya && options.jiyaConfig.halfWidth === "margin") {
setOwnedAttribute(el, "data-jz-halfwidth", "margin", ownerToken);
}
if (!options.justifyAtoms) {
setOwnedAttribute(el, "data-jz-atoms", "inline", ownerToken);
}
}
function rootsWithin(el) {
return Array.from(renderedRoots).filter((root) => root === el || el.contains(root)).sort((a, b) => a.contains(b) ? 1 : b.contains(a) ? -1 : 0);
}
function unwindRoots(roots) {
for (const root of roots) {
revertPasses(PASSES, makeCtx(root));
revertOwnedAttribute(root, "data-jz-halfwidth", ownerToken);
revertOwnedAttribute(root, "data-jz-atoms", ownerToken);
renderedRoots.delete(root);
finder.invalidate(root);
}
}
function revertImpl(el) {
const roots = rootsWithin(el);
if (roots.length === 0) {
revertPasses(PASSES, makeCtx(el));
} else {
unwindRoots(roots);
}
finder.invalidate(el);
}
let observer = null;
let observeRoot = null;
let observerPauseDepth = 0;
let observerActive = false;
const expectedClassMutations = [];
let expectedClassMutationHead = 0;
const observeConfig = {
subtree: true,
childList: true,
characterData: true,
attributes: true,
attributeOldValue: true,
attributeFilter: OBSERVE_ATTRS.slice()
};
function clearExpectedClassMutations() {
expectedClassMutations.length = 0;
expectedClassMutationHead = 0;
}
function setOwnedClass(element, className, present) {
if (!ownedElements.has(element) || element.classList.contains(className) === present) {
return;
}
const oldValue = element.getAttribute("class");
const tokens = Array.from(element.classList).filter((token) => token !== className);
if (present) {
tokens.push(className);
}
const newValue = tokens.join(" ");
const shouldExpect = observerActive && observerPauseDepth === 0 && !!observeRoot && observeRoot.contains(element);
const expectation = shouldExpect ? { target: element, oldValue, newValue } : null;
if (expectation) {
expectedClassMutations.push(expectation);
}
try {
if (present) {
element.classList.add(className);
} else {
element.classList.remove(className);
}
} catch (error) {
if (expectation) {
expectedClassMutations.pop();
}
throw error;
}
if (expectation && element.getAttribute("class") !== newValue) {
expectedClassMutations.pop();
}
}
function classMutationNewValues(records) {
const values = new Array(records.length);
const nextValues = /* @__PURE__ */ new Map();
for (let i = records.length - 1; i >= 0; i -= 1) {
const record = records[i];
if (record.type !== "attributes" || record.attributeName !== "class") {
continue;
}
const target = record.target;
values[i] = nextValues.has(target) ? nextValues.get(target) : target.getAttribute("class");
nextValues.set(target, record.oldValue);
}
return values;
}
function consumeExpectedClassMutation(record, newValue) {
if (record.type !== "attributes" || record.attributeName !== "class") {
return false;
}
const expected = expectedClassMutations[expectedClassMutationHead];
if (!expected || expected.target !== record.target || expected.oldValue !== record.oldValue || expected.newValue !== newValue) {
return false;
}
expectedClassMutationHead += 1;
return true;
}
function compactExpectedClassMutations() {
if (expectedClassMutationHead === 0) {
return;
}
if (expectedClassMutationHead === expectedClassMutations.length) {
clearExpectedClassMutations();
return;
}
if (expectedClassMutationHead >= 1024 && expectedClassMutationHead * 2 >= expectedClassMutations.length) {
expectedClassMutations.splice(0, expectedClassMutationHead);
expectedClassMutationHead = 0;
}
}
function pauseObserver() {
if (observerPauseDepth === 0) {
observerActive = false;
if (observer) {
observer.disconnect();
}
clearExpectedClassMutations();
}
observerPauseDepth += 1;
}
function resumeObserver() {
if (observerPauseDepth <= 0) {
return;
}
observerPauseDepth -= 1;
if (observerPauseDepth === 0 && observer && observeRoot) {
observer.takeRecords();
clearExpectedClassMutations();
observer.observe(observeRoot, observeConfig);
observerActive = true;
}
}
function withObserverPaused(run) {
pauseObserver();
try {
return run();
} finally {
resumeObserver();
}
}
function nearestBlock(node) {
let e = node.nodeType === 1 ? node : node.parentElement;
while (e) {
if (options.finder.blockTags.has(e.nodeName)) {
return e;
}
if (e === observeRoot) {
break;
}
e = e.parentElement;
}
return observeRoot;
}
function onMutations(records) {
const rerenderBlocks = /* @__PURE__ */ new Set();
const renderNew = [];
const removedRoots = /* @__PURE__ */ new Set();
const classNewValues = classMutationNewValues(records);
try {
for (let recordIndex = 0; recordIndex < records.length; recordIndex += 1) {
const m = records[recordIndex];
if (consumeExpectedClassMutation(m, classNewValues[recordIndex])) {
continue;
}
if (m.type === "childList") {
m.addedNodes.forEach((n) => {
if (n.nodeType === 1 && options.finder.blockTags.has(n.nodeName)) {
const np = n;
const P = np.parentElement;
const pIsBlock = !!P && options.finder.blockTags.has(P.nodeName);
const pHadLeafOrphan = !!P && Array.prototype.some.call(
P.children,
(c) => c.nodeName === "JZ-ORPHAN"
);
if (pIsBlock && pHadLeafOrphan) {
rerenderBlocks.add(P);
} else if (!pIsBlock && P) {
const b = nearestBlock(P);
if (b) {
rerenderBlocks.add(b);
}
} else {
renderNew.push(np);
}
} else if (n.nodeType === 1 || n.nodeType === 3) {
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
});
if (m.removedNodes.length) {
m.removedNodes.forEach((node) => {
if (node.nodeType !== 1) {
return;
}
for (const root of rootsWithin(node)) {
removedRoots.add(root);
}
});
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
} else {
const b = nearestBlock(m.target);
if (b) {
rerenderBlocks.add(b);
}
}
}
} finally {
compactExpectedClassMutations();
}
if (!rerenderBlocks.size && !renderNew.length && !removedRoots.size) {
return;
}
withObserverPaused(() => {
unwindRoots(Array.from(removedRoots).sort((a, b) => a.contains(b) ? 1 : b.contains(a) ? -1 : 0));
const all = Array.from(rerenderBlocks).filter((b) => b.isConnected);
const blocks = all.filter((b) => !all.some((o) => o !== b && o.contains(b)));
for (const b of blocks) {
revertImpl(b);
renderImpl(b);
}
for (const el of renderNew) {
if (!el.isConnected) {
continue;
}
if (blocks.some((b) => b.contains(el))) {
continue;
}
renderImpl(el);
}
});
}
return {
options,
render(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => renderImpl(el));
},
revert(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => revertImpl(el));
},
rerender(root) {
const el = resolveRoot(root, options);
if (!el) {
return;
}
withObserverPaused(() => {
revertImpl(el);
renderImpl(el);
});
},
invalidate(root) {
const el = resolveRoot(root, options);
if (el) {
finder.invalidate(el);
}
},
observe(root) {
if (typeof MutationObserver === "undefined") {
return;
}
const el = resolveRoot(root, options);
if (!el) {
return;
}
observerActive = false;
if (observer) {
observer.disconnect();
}
clearExpectedClassMutations();
observeRoot = el;
observer = new MutationObserver(onMutations);
if (observerPauseDepth === 0) {
observer.observe(el, observeConfig);
observerActive = true;
}
},
disconnect() {
observerActive = false;
if (observer) {
observer.disconnect();
observer = null;
observeRoot = null;
}
clearExpectedClassMutations();
}
};
}
return __toCommonJS(index_exports);
})();
if(typeof globalThis!=='undefined'){globalThis.Juzhen=Juzhen;}