@longd/layout-ui 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -1
- package/dist/CATEditor-C-b6vybW.d.cts +381 -0
- package/dist/CATEditor-CLp6jZAf.d.ts +381 -0
- package/dist/chunk-BLJWR4ZV.js +11 -0
- package/dist/chunk-BLJWR4ZV.js.map +1 -0
- package/dist/{chunk-CZ3IMHZ6.js → chunk-H7SY4VJU.js} +7 -11
- package/dist/chunk-H7SY4VJU.js.map +1 -0
- package/dist/chunk-YXQGAND3.js +137 -0
- package/dist/chunk-YXQGAND3.js.map +1 -0
- package/dist/chunk-ZME2TTK5.js +2527 -0
- package/dist/chunk-ZME2TTK5.js.map +1 -0
- package/dist/index.cjs +2612 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +504 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -2
- package/dist/layout/cat-editor.cjs +2669 -0
- package/dist/layout/cat-editor.cjs.map +1 -0
- package/dist/layout/cat-editor.css +504 -0
- package/dist/layout/cat-editor.css.map +1 -0
- package/dist/layout/cat-editor.d.cts +28 -0
- package/dist/layout/cat-editor.d.ts +28 -0
- package/dist/layout/cat-editor.js +29 -0
- package/dist/layout/cat-editor.js.map +1 -0
- package/dist/layout/select.cjs +2 -1
- package/dist/layout/select.cjs.map +1 -1
- package/dist/layout/select.js +2 -1
- package/dist/utils/detect-quotes.cjs +162 -0
- package/dist/utils/detect-quotes.cjs.map +1 -0
- package/dist/utils/detect-quotes.d.cts +88 -0
- package/dist/utils/detect-quotes.d.ts +88 -0
- package/dist/utils/detect-quotes.js +9 -0
- package/dist/utils/detect-quotes.js.map +1 -0
- package/package.json +39 -3
- package/dist/chunk-CZ3IMHZ6.js.map +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/utils/detect-quotes.ts
|
|
2
|
+
var BUILTIN_ESCAPE_PATTERNS = {
|
|
3
|
+
english: [
|
|
4
|
+
"n't",
|
|
5
|
+
// don't, can't, won't, shouldn't, …
|
|
6
|
+
"'s",
|
|
7
|
+
// it's, he's, she's, …
|
|
8
|
+
"'re",
|
|
9
|
+
// they're, we're, you're, …
|
|
10
|
+
"'ve",
|
|
11
|
+
// I've, they've, we've, …
|
|
12
|
+
"'ll",
|
|
13
|
+
// I'll, you'll, they'll, …
|
|
14
|
+
"'m",
|
|
15
|
+
// I'm
|
|
16
|
+
"'d"
|
|
17
|
+
// I'd, they'd, …
|
|
18
|
+
],
|
|
19
|
+
default: []
|
|
20
|
+
};
|
|
21
|
+
function resolveEscapeSuffixes(opts) {
|
|
22
|
+
const patternsOpt = opts.escapePatterns ?? "english";
|
|
23
|
+
let patterns;
|
|
24
|
+
if (typeof patternsOpt === "string") {
|
|
25
|
+
patterns = BUILTIN_ESCAPE_PATTERNS;
|
|
26
|
+
return patterns[patternsOpt] ?? [];
|
|
27
|
+
}
|
|
28
|
+
return Object.values(patternsOpt).flat();
|
|
29
|
+
}
|
|
30
|
+
function isContractionApostrophe(text, index, suffixes) {
|
|
31
|
+
for (const suffix of suffixes) {
|
|
32
|
+
const apostrophePositions = [];
|
|
33
|
+
for (let i = 0; i < suffix.length; i++) {
|
|
34
|
+
if (suffix[i] === "'") apostrophePositions.push(i);
|
|
35
|
+
}
|
|
36
|
+
for (const ap of apostrophePositions) {
|
|
37
|
+
const suffixStart = index - ap;
|
|
38
|
+
if (suffixStart < 0) continue;
|
|
39
|
+
const suffixEnd = suffixStart + suffix.length;
|
|
40
|
+
if (suffixEnd > text.length) continue;
|
|
41
|
+
const slice = text.slice(suffixStart, suffixEnd);
|
|
42
|
+
if (slice !== suffix) continue;
|
|
43
|
+
if (suffixStart > 0 && /\w/.test(text[suffixStart - 1])) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function detectQuotes(text, options = {}) {
|
|
51
|
+
const {
|
|
52
|
+
escapeContractions = true,
|
|
53
|
+
allowNesting = false,
|
|
54
|
+
detectInnerQuotes = true
|
|
55
|
+
} = options;
|
|
56
|
+
const escapeSuffixes = escapeContractions ? resolveEscapeSuffixes(options) : [];
|
|
57
|
+
const result = /* @__PURE__ */ new Map();
|
|
58
|
+
let openSingle = null;
|
|
59
|
+
let openDouble = null;
|
|
60
|
+
for (let i = 0; i < text.length; i++) {
|
|
61
|
+
const ch = text[i];
|
|
62
|
+
if (ch === "\\") {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === '"') {
|
|
67
|
+
if (!allowNesting && !detectInnerQuotes && openSingle) continue;
|
|
68
|
+
if (openDouble) {
|
|
69
|
+
if (!allowNesting && openSingle && openSingle.start > openDouble.start) {
|
|
70
|
+
openSingle = null;
|
|
71
|
+
}
|
|
72
|
+
const range = {
|
|
73
|
+
start: openDouble.start,
|
|
74
|
+
end: i,
|
|
75
|
+
quoteType: "double",
|
|
76
|
+
content: text.slice(openDouble.start + 1, i),
|
|
77
|
+
closed: true
|
|
78
|
+
};
|
|
79
|
+
result.set(openDouble.start, range);
|
|
80
|
+
result.set(i, range);
|
|
81
|
+
openDouble = null;
|
|
82
|
+
} else {
|
|
83
|
+
openDouble = { start: i };
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === "'") {
|
|
88
|
+
if (!allowNesting && !detectInnerQuotes && openDouble) continue;
|
|
89
|
+
if (escapeContractions && escapeSuffixes.length > 0 && isContractionApostrophe(text, i, escapeSuffixes)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (openSingle) {
|
|
93
|
+
if (!allowNesting && openDouble && openDouble.start > openSingle.start) {
|
|
94
|
+
openDouble = null;
|
|
95
|
+
}
|
|
96
|
+
const range = {
|
|
97
|
+
start: openSingle.start,
|
|
98
|
+
end: i,
|
|
99
|
+
quoteType: "single",
|
|
100
|
+
content: text.slice(openSingle.start + 1, i),
|
|
101
|
+
closed: true
|
|
102
|
+
};
|
|
103
|
+
result.set(openSingle.start, range);
|
|
104
|
+
result.set(i, range);
|
|
105
|
+
openSingle = null;
|
|
106
|
+
} else {
|
|
107
|
+
openSingle = { start: i };
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (openSingle) {
|
|
113
|
+
result.set(openSingle.start, {
|
|
114
|
+
start: openSingle.start,
|
|
115
|
+
end: null,
|
|
116
|
+
quoteType: "single",
|
|
117
|
+
content: text.slice(openSingle.start + 1),
|
|
118
|
+
closed: false
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (openDouble) {
|
|
122
|
+
result.set(openDouble.start, {
|
|
123
|
+
start: openDouble.start,
|
|
124
|
+
end: null,
|
|
125
|
+
quoteType: "double",
|
|
126
|
+
content: text.slice(openDouble.start + 1),
|
|
127
|
+
closed: false
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
BUILTIN_ESCAPE_PATTERNS,
|
|
135
|
+
detectQuotes
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=chunk-YXQGAND3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/detect-quotes.ts"],"sourcesContent":["// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type QuoteType = 'single' | 'double'\n\nexport interface QuoteRange {\n /** Character index where the opening quote sits */\n start: number\n /** Character index where the closing quote sits – `null` when unclosed */\n end: number | null\n /** Which kind of quote */\n quoteType: QuoteType\n /** The text between the quotes (empty string when unclosed) */\n content: string\n /** Whether the quote pair is properly closed */\n closed: boolean\n}\n\n/**\n * Language-specific patterns whose trailing single-quote should NOT be treated\n * as a quote delimiter.\n *\n * Each value is an array of suffixes that, when found immediately before a `'`,\n * cause that apostrophe to be skipped.\n *\n * Example – `\"english\"` ships with common English contractions:\n * `don't`, `can't`, `won't`, `isn't`, `it's`, …\n */\nexport interface EscapePatterns {\n [language: string]: Array<string>\n}\n\nexport const BUILTIN_ESCAPE_PATTERNS: EscapePatterns = {\n english: [\n \"n't\", // don't, can't, won't, shouldn't, …\n \"'s\", // it's, he's, she's, …\n \"'re\", // they're, we're, you're, …\n \"'ve\", // I've, they've, we've, …\n \"'ll\", // I'll, you'll, they'll, …\n \"'m\", // I'm\n \"'d\", // I'd, they'd, …\n ],\n default: [],\n}\n\nexport interface DetectQuotesOptions {\n /**\n * When `true` (the default), enable contraction-aware escaping so that\n * apostrophes inside words like `don't` are not treated as quote delimiters.\n */\n escapeContractions?: boolean\n\n /**\n * Which set of escape patterns to apply.\n * Pass a key of `BUILTIN_ESCAPE_PATTERNS` (e.g. `\"english\"`) or supply\n * your own object conforming to `EscapePatterns`.\n *\n * @default \"english\"\n */\n escapePatterns?: string | EscapePatterns\n\n /**\n * When `true`, allow independent tracking of both quote types so they can\n * overlap (e.g. `\"text 'a b\" c'` produces two overlapping ranges).\n *\n * When `false` (the default), properly nested quotes are still detected,\n * but if an inner quote of the other type has not closed by the time the\n * outer quote closes, the inner pending quote is discarded to prevent\n * overlapping ranges.\n *\n * @default false\n */\n allowNesting?: boolean\n\n /**\n * When `true` (the default), quotes of the other type that open *and close*\n * inside an already-open quote are detected as separate ranges\n * (e.g. `'run away'` inside `\"she told me 'run away' before dawn\"`).\n *\n * When `false`, any quote character of the other type is treated as plain\n * text while an outer quote is open — no inner ranges are produced.\n *\n * Has no effect when `allowNesting` is `true` (everything is tracked\n * independently in that mode).\n *\n * @default true\n */\n detectInnerQuotes?: boolean\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the effective escape-suffix list from the options bag.\n */\nfunction resolveEscapeSuffixes(opts: DetectQuotesOptions): Array<string> {\n const patternsOpt = opts.escapePatterns ?? 'english'\n\n let patterns: EscapePatterns\n if (typeof patternsOpt === 'string') {\n patterns = BUILTIN_ESCAPE_PATTERNS\n return patterns[patternsOpt] ?? []\n }\n\n // Merge all provided language arrays into one flat list\n return Object.values(patternsOpt).flat()\n}\n\n/**\n * Returns `true` when the single-quote at `index` is part of a known\n * contraction / possessive pattern and should be skipped.\n */\nfunction isContractionApostrophe(\n text: string,\n index: number,\n suffixes: Array<string>,\n): boolean {\n for (const suffix of suffixes) {\n // The suffix already contains the apostrophe (e.g. \"n't\", \"'s\").\n // We need to figure out where the apostrophe sits inside `suffix`.\n const apostrophePositions: Array<number> = []\n for (let i = 0; i < suffix.length; i++) {\n if (suffix[i] === \"'\") apostrophePositions.push(i)\n }\n\n for (const ap of apostrophePositions) {\n // Start position of the full suffix in `text`\n const suffixStart = index - ap\n if (suffixStart < 0) continue\n const suffixEnd = suffixStart + suffix.length\n if (suffixEnd > text.length) continue\n\n const slice = text.slice(suffixStart, suffixEnd)\n if (slice !== suffix) continue\n\n // The character before the suffix must be a word character\n // (otherwise `'t` at the start of a string would match).\n if (suffixStart > 0 && /\\w/.test(text[suffixStart - 1])) {\n return true\n }\n }\n }\n return false\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\n/**\n * Scan `text` and return every quote range found.\n *\n * The returned `Map` is keyed by **both** the start and end position indices\n * of each quote, so you can look up a `QuoteRange` by either boundary.\n * For unclosed quotes only the start key is stored (end is `null`).\n *\n * ```ts\n * const result = detectQuotes(`She said \"hello\" and 'goodbye'`);\n * // Map {\n * // 10 => { start: 10, end: 16, quoteType: \"double\", content: \"hello\", closed: true },\n * // 16 => { start: 10, end: 16, quoteType: \"double\", content: \"hello\", closed: true },\n * // 22 => { start: 22, end: 30, quoteType: \"single\", content: \"goodbye\", closed: true },\n * // 30 => { start: 22, end: 30, quoteType: \"single\", content: \"goodbye\", closed: true },\n * // }\n * ```\n */\nexport function detectQuotes(\n text: string,\n options: DetectQuotesOptions = {},\n): Map<number, QuoteRange> {\n const {\n escapeContractions = true,\n allowNesting = false,\n detectInnerQuotes = true,\n } = options\n\n const escapeSuffixes = escapeContractions\n ? resolveEscapeSuffixes(options)\n : []\n\n const result = new Map<number, QuoteRange>()\n\n // We maintain a small stack so that nested quotes work correctly.\n // Only one level per quote type can be \"open\" at a time.\n let openSingle: { start: number } | null = null\n let openDouble: { start: number } | null = null\n\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n\n // ── Handle backslash escapes (e.g. \\\" or \\') ──────────────────────────\n if (ch === '\\\\') {\n i++ // skip the escaped character\n continue\n }\n\n // ── Double quote ──────────────────────────────────────────────────────\n if (ch === '\"') {\n // When inner detection is off (and not in free-nesting mode),\n // skip entirely if we’re inside an open single quote.\n if (!allowNesting && !detectInnerQuotes && openSingle) continue\n\n if (openDouble) {\n // When nesting is off, discard any inner single quote that started\n // after this double quote opened — it would create an overlap.\n if (\n !allowNesting &&\n openSingle &&\n openSingle.start > openDouble.start\n ) {\n openSingle = null\n }\n\n // Close the current double-quoted range\n const range: QuoteRange = {\n start: openDouble.start,\n end: i,\n quoteType: 'double',\n content: text.slice(openDouble.start + 1, i),\n closed: true,\n }\n result.set(openDouble.start, range)\n result.set(i, range)\n openDouble = null\n } else {\n // Open a new double-quoted range\n openDouble = { start: i }\n }\n continue\n }\n\n // ── Single quote / apostrophe ─────────────────────────────────────────\n if (ch === \"'\") {\n // When inner detection is off (and not in free-nesting mode),\n // skip entirely if we’re inside an open double quote.\n if (!allowNesting && !detectInnerQuotes && openDouble) continue\n\n // Check contraction / possessive escaping BEFORE treating as quote\n if (\n escapeContractions &&\n escapeSuffixes.length > 0 &&\n isContractionApostrophe(text, i, escapeSuffixes)\n ) {\n continue // skip – this apostrophe belongs to a contraction\n }\n\n if (openSingle) {\n // When nesting is off, discard any inner double quote that started\n // after this single quote opened — it would create an overlap.\n if (\n !allowNesting &&\n openDouble &&\n openDouble.start > openSingle.start\n ) {\n openDouble = null\n }\n\n // Close the current single-quoted range\n const range: QuoteRange = {\n start: openSingle.start,\n end: i,\n quoteType: 'single',\n content: text.slice(openSingle.start + 1, i),\n closed: true,\n }\n result.set(openSingle.start, range)\n result.set(i, range)\n openSingle = null\n } else {\n // Open a new single-quoted range\n openSingle = { start: i }\n }\n continue\n }\n }\n\n // ── Record any unclosed quotes ────────────────────────────────────────────\n if (openSingle) {\n result.set(openSingle.start, {\n start: openSingle.start,\n end: null,\n quoteType: 'single',\n content: text.slice(openSingle.start + 1),\n closed: false,\n })\n }\n\n if (openDouble) {\n result.set(openDouble.start, {\n start: openDouble.start,\n end: null,\n quoteType: 'double',\n content: text.slice(openDouble.start + 1),\n closed: false,\n })\n }\n\n return result\n}\n"],"mappings":"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","names":[]}
|