@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
package/README.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
# @longd/layout-ui
|
|
2
2
|
|
|
3
|
-
A React component library featuring a powerful
|
|
3
|
+
A React component library featuring a powerful select component, a CAT (Computer-Assisted Translation) rich-text editor with rule-based highlighting, and a quote-detection utility. Built with **Tailwind CSS v4**.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
- **Virtualized Select** — LayoutSelect with drag-and-drop, virtualization, and custom rendering.
|
|
8
|
+
- **CAT Editor** — Lexical-based rich-text editor with spell-check, glossary/keyword highlighting, tag collapsing, quote detection, link detection, and @mention support.
|
|
9
|
+
- **Detect Quotes** — Standalone utility for scanning text and returning all single/double quote ranges with contraction-aware escaping.
|
|
10
|
+
|
|
11
|
+
## Demo
|
|
12
|
+
|
|
13
|
+
- **Full demo site**: https://layout-ui-seven.vercel.app
|
|
14
|
+
- **LayoutSelect component**: https://layout-ui-seven.vercel.app/demo/layout-select
|
|
15
|
+
- **CAT Editor**: https://layout-ui-seven.vercel.app/demo/cat-editor
|
|
16
|
+
- **Detect Quotes**: https://layout-ui-seven.vercel.app/demo/detect-quotes
|
|
17
|
+
|
|
18
|
+
## LayoutSelect Features
|
|
19
|
+
|
|
20
|
+
Below are the features for the `LayoutSelect` demo (sourced from `src/data/layout-demos.tsx`):
|
|
21
|
+
|
|
7
22
|
- **Single & multiple selection** with chip-based display
|
|
8
23
|
- **Virtualized list** via `@tanstack/react-virtual` — handles 10,000+ options smoothly
|
|
9
24
|
- **Drag-and-drop sorting** (flat & grouped) via `@dnd-kit`
|
|
@@ -134,8 +149,20 @@ function App() {
|
|
|
134
149
|
### Deep import (tree-shaking)
|
|
135
150
|
|
|
136
151
|
```tsx
|
|
152
|
+
// LayoutSelect
|
|
137
153
|
import { LayoutSelect } from '@longd/layout-ui/layout/select'
|
|
138
154
|
import type { IOption } from '@longd/layout-ui/layout/select'
|
|
155
|
+
|
|
156
|
+
// CATEditor
|
|
157
|
+
import { CATEditor } from '@longd/layout-ui/layout/cat-editor'
|
|
158
|
+
import type {
|
|
159
|
+
CATEditorProps,
|
|
160
|
+
MooRule,
|
|
161
|
+
} from '@longd/layout-ui/layout/cat-editor'
|
|
162
|
+
|
|
163
|
+
// Detect Quotes
|
|
164
|
+
import { detectQuotes } from '@longd/layout-ui/utils/detect-quotes'
|
|
165
|
+
import type { QuoteRange } from '@longd/layout-ui/utils/detect-quotes'
|
|
139
166
|
```
|
|
140
167
|
|
|
141
168
|
---
|
|
@@ -328,6 +355,235 @@ const [options, setOptions] = useState<IOption[]>(initialOptions)
|
|
|
328
355
|
|
|
329
356
|
---
|
|
330
357
|
|
|
358
|
+
## CATEditor
|
|
359
|
+
|
|
360
|
+
A Lexical-based rich-text editor designed for Computer-Assisted Translation workflows. Supports rule-based highlighting with popovers, tag collapsing, smart quote replacement, link detection, and @mention typeahead.
|
|
361
|
+
|
|
362
|
+
### Quick start
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import { CATEditor } from '@longd/layout-ui/layout/cat-editor'
|
|
366
|
+
import type { MooRule, CATEditorRef } from '@longd/layout-ui/layout/cat-editor'
|
|
367
|
+
|
|
368
|
+
const rules: MooRule[] = [
|
|
369
|
+
{
|
|
370
|
+
type: 'spellcheck',
|
|
371
|
+
validations: [
|
|
372
|
+
{
|
|
373
|
+
categoryId: 'spelling',
|
|
374
|
+
start: 0,
|
|
375
|
+
end: 5,
|
|
376
|
+
content: 'Helo',
|
|
377
|
+
message: 'Possible spelling mistake',
|
|
378
|
+
shortMessage: 'Spelling',
|
|
379
|
+
suggestions: [{ value: 'Hello' }],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
function App() {
|
|
386
|
+
return (
|
|
387
|
+
<CATEditor
|
|
388
|
+
initialText="Helo world"
|
|
389
|
+
rules={rules}
|
|
390
|
+
onChange={(text) => console.log(text)}
|
|
391
|
+
/>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### CSS requirement
|
|
397
|
+
|
|
398
|
+
CATEditor ships its own CSS. When using the deep import, the CSS is imported automatically from the entry point. Make sure your bundler supports CSS imports.
|
|
399
|
+
|
|
400
|
+
### `<CATEditor>` props
|
|
401
|
+
|
|
402
|
+
| Prop | Type | Default | Description |
|
|
403
|
+
| ---------------------- | ----------------------------------------------- | ----------------- | -------------------------------------------------------------------------- |
|
|
404
|
+
| `initialText` | `string` | `''` | Initial text content for the editor. |
|
|
405
|
+
| `rules` | `MooRule[]` | `[]` | Rules to apply for highlighting. |
|
|
406
|
+
| `onChange` | `(text: string) => void` | — | Called when editor content changes. |
|
|
407
|
+
| `onSuggestionApply` | `(ruleId, suggestion, range, ruleType) => void` | — | Called when a spell-check suggestion is applied. |
|
|
408
|
+
| `codepointDisplayMap` | `Record<number, string>` | — | Custom code-point → display symbol map, merged on top of the built-in map. |
|
|
409
|
+
| `renderPopoverContent` | `PopoverContentRenderer` | — | Custom renderer for popover content per annotation. |
|
|
410
|
+
| `onLinkClick` | `(url: string) => void` | — | Called when a link highlight is clicked. |
|
|
411
|
+
| `openLinksOnClick` | `boolean` | `true` | Whether clicking a link highlight opens the URL. |
|
|
412
|
+
| `onMentionClick` | `(userId, userName) => void` | — | Called when a mention node is clicked. |
|
|
413
|
+
| `onMentionInsert` | `(user: IMentionUser) => void` | — | Called when a mention is inserted via the typeahead. |
|
|
414
|
+
| `mentionSerialize` | `(id: string) => string` | `` `@{${id}}` `` | Converts a mention ID to model text. |
|
|
415
|
+
| `mentionPattern` | `RegExp` | `/@\{([^}]+)\}/g` | RegExp to detect mention patterns in pasted/imported text. |
|
|
416
|
+
| `renderMentionDOM` | `MentionDOMRenderer` | — | Custom DOM renderer for mention nodes. |
|
|
417
|
+
| `placeholder` | `string` | `'Start typing…'` | Placeholder text. |
|
|
418
|
+
| `className` | `string` | — | Additional class name for the editor container. |
|
|
419
|
+
| `dir` | `'ltr' \| 'rtl' \| 'auto'` | `'ltr'` | Text direction. |
|
|
420
|
+
| `jpFont` | `boolean` | `false` | When `true`, applies a Japanese-optimised font stack. |
|
|
421
|
+
| `editable` | `boolean` | `true` | Whether the editor content is editable. |
|
|
422
|
+
| `readOnlySelectable` | `boolean` | `false` | When `editable` is `false`, still allows caret/selection and copy. |
|
|
423
|
+
| `onKeyDown` | `(event: KeyboardEvent) => boolean` | — | Custom keydown handler. Return `true` to prevent default handling. |
|
|
424
|
+
|
|
425
|
+
### `CATEditorRef` (imperative API)
|
|
426
|
+
|
|
427
|
+
Access via `ref`:
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
const editorRef = useRef<CATEditorRef>(null)
|
|
431
|
+
<CATEditor ref={editorRef} ... />
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
| Method | Description |
|
|
435
|
+
| ------------------------------------------- | --------------------------------------------------- |
|
|
436
|
+
| `insertText(text)` | Insert text at the current cursor position. |
|
|
437
|
+
| `focus()` | Focus the editor. |
|
|
438
|
+
| `getText()` | Get the full plain-text content. |
|
|
439
|
+
| `replaceAll(search, replacement)` | Replace all occurrences. Returns count. |
|
|
440
|
+
| `flashHighlight(annotationId, durationMs?)` | Flash-highlight elements matching an annotation ID. |
|
|
441
|
+
| `clearFlash()` | Remove any active flash highlight. |
|
|
442
|
+
|
|
443
|
+
### Rule types (`MooRule`)
|
|
444
|
+
|
|
445
|
+
| Type | Interface | Description |
|
|
446
|
+
| ---------------- | ------------------ | ------------------------------------------------------------------------ |
|
|
447
|
+
| `'spellcheck'` | `ISpellCheckRule` | Spell-check validations with suggestions. |
|
|
448
|
+
| `'glossary'` | `IKeywordsRule` | Generic keyword/term highlighting (glossary, LexiQA, TB target, search). |
|
|
449
|
+
| `'special-char'` | `ISpecialCharRule` | Highlight special/invisible characters with popover names. |
|
|
450
|
+
| `'tag'` | `ITagRule` | Detect and optionally collapse HTML tags / placeholders. |
|
|
451
|
+
| `'quote'` | `IQuoteRule` | Smart quote detection and replacement (uses `detectQuotes` internally). |
|
|
452
|
+
| `'link'` | `ILinkRule` | Detect and highlight URLs. |
|
|
453
|
+
| `'mention'` | `IMentionRule` | @mention typeahead with user list. |
|
|
454
|
+
|
|
455
|
+
### Examples
|
|
456
|
+
|
|
457
|
+
#### Keyword / glossary highlighting
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
const rules: MooRule[] = [
|
|
461
|
+
{
|
|
462
|
+
type: 'glossary',
|
|
463
|
+
label: 'tb-target',
|
|
464
|
+
entries: [
|
|
465
|
+
{ term: 'React', description: 'A JavaScript library for building UIs' },
|
|
466
|
+
{ term: 'Tailwind', pattern: 'tailwind(css)?' },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
]
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### Tag collapsing
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
const rules: MooRule[] = [
|
|
476
|
+
{
|
|
477
|
+
type: 'tag',
|
|
478
|
+
collapsed: true,
|
|
479
|
+
collapseScope: 'html-only',
|
|
480
|
+
},
|
|
481
|
+
]
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### Smart quote replacement
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
const rules: MooRule[] = [
|
|
488
|
+
{
|
|
489
|
+
type: 'quote',
|
|
490
|
+
singleQuote: { opening: '\u2018', closing: '\u2019' },
|
|
491
|
+
doubleQuote: { opening: '\u201C', closing: '\u201D' },
|
|
492
|
+
},
|
|
493
|
+
]
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### @mention typeahead
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
const rules: MooRule[] = [
|
|
500
|
+
{
|
|
501
|
+
type: 'mention',
|
|
502
|
+
users: [
|
|
503
|
+
{ id: '1', name: 'Alice' },
|
|
504
|
+
{ id: '2', name: 'Bob' },
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
]
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Detect Quotes
|
|
513
|
+
|
|
514
|
+
A standalone, framework-agnostic utility for scanning text and returning all single and double quote ranges. Handles nested quotes, backslash escapes, and English contractions (`don't`, `it's`, etc.).
|
|
515
|
+
|
|
516
|
+
### Quick start
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
import { detectQuotes } from '@longd/layout-ui/utils/detect-quotes'
|
|
520
|
+
|
|
521
|
+
const result = detectQuotes(`She said "hello" and 'goodbye'`)
|
|
522
|
+
|
|
523
|
+
// Map keyed by both start and end indices:
|
|
524
|
+
// 10 => { start: 10, end: 16, quoteType: "double", content: "hello", closed: true }
|
|
525
|
+
// 16 => { start: 10, end: 16, quoteType: "double", content: "hello", closed: true }
|
|
526
|
+
// 22 => { start: 22, end: 30, quoteType: "single", content: "goodbye", closed: true }
|
|
527
|
+
// 30 => { start: 22, end: 30, quoteType: "single", content: "goodbye", closed: true }
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### `detectQuotes(text, options?)`
|
|
531
|
+
|
|
532
|
+
Returns a `Map<number, QuoteRange>` keyed by both start and end position indices.
|
|
533
|
+
|
|
534
|
+
### `QuoteRange`
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
interface QuoteRange {
|
|
538
|
+
start: number // Index of the opening quote
|
|
539
|
+
end: number | null // Index of the closing quote (null when unclosed)
|
|
540
|
+
quoteType: 'single' | 'double'
|
|
541
|
+
content: string // Text between the quotes
|
|
542
|
+
closed: boolean // Whether the quote pair is properly closed
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### `DetectQuotesOptions`
|
|
547
|
+
|
|
548
|
+
| Option | Type | Default | Description |
|
|
549
|
+
| -------------------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------ |
|
|
550
|
+
| `escapeContractions` | `boolean` | `true` | Skip apostrophes in contractions (`don't`, `it's`). |
|
|
551
|
+
| `escapePatterns` | `string \| EscapePatterns` | `'english'` | Which contraction patterns to apply. Pass `'english'` or a custom `EscapePatterns` object. |
|
|
552
|
+
| `allowNesting` | `boolean` | `false` | Allow independent tracking of both quote types (can overlap). |
|
|
553
|
+
| `detectInnerQuotes` | `boolean` | `true` | Detect quotes of the other type inside an already-open quote. |
|
|
554
|
+
|
|
555
|
+
### `BUILTIN_ESCAPE_PATTERNS`
|
|
556
|
+
|
|
557
|
+
Pre-built contraction patterns for English: `n't`, `'s`, `'re`, `'ve`, `'ll`, `'m`, `'d`.
|
|
558
|
+
|
|
559
|
+
### Examples
|
|
560
|
+
|
|
561
|
+
#### With contractions
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
// Apostrophes in "don't" and "it's" are skipped
|
|
565
|
+
const result = detectQuotes(`He said "don't worry, it's fine"`)
|
|
566
|
+
// Only the double-quoted range is detected
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
#### Nested quotes
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
const result = detectQuotes(`"she told me 'run away' before dawn"`)
|
|
573
|
+
// Detects both the outer double-quoted and inner single-quoted ranges
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Disable contraction escaping
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const result = detectQuotes(`it's a 'test'`, {
|
|
580
|
+
escapeContractions: false,
|
|
581
|
+
})
|
|
582
|
+
// The apostrophe in "it's" is treated as a quote delimiter
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
331
587
|
## Adding new components
|
|
332
588
|
|
|
333
589
|
This library is structured for easy expansion. To add a new component:
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
import { DetectQuotesOptions } from './utils/detect-quotes.cjs';
|
|
4
|
+
import { TextNode, Spread, SerializedTextNode, NodeKey, EditorConfig, DOMExportOutput, DOMConversionMap, LexicalNode } from 'lexical';
|
|
5
|
+
|
|
6
|
+
interface ISuggestion {
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
interface ISpellCheckValidation {
|
|
10
|
+
categoryId: string;
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
content: string;
|
|
14
|
+
message: string;
|
|
15
|
+
shortMessage: string;
|
|
16
|
+
suggestions: Array<ISuggestion>;
|
|
17
|
+
dictionaries?: Array<string>;
|
|
18
|
+
}
|
|
19
|
+
interface ISpellCheckRule {
|
|
20
|
+
type: 'spellcheck';
|
|
21
|
+
validations: Array<ISpellCheckValidation>;
|
|
22
|
+
}
|
|
23
|
+
interface IKeywordsEntry {
|
|
24
|
+
/** The term text used for exact string matching and display. */
|
|
25
|
+
term: string;
|
|
26
|
+
/** Optional description shown in the popover. */
|
|
27
|
+
description?: string;
|
|
28
|
+
/** Optional regex source string. When provided, it is used for matching
|
|
29
|
+
* instead of an exact `term` lookup. The `g` flag is added automatically.
|
|
30
|
+
* Example: `'hello|hi'` matches both "hello" and "hi". */
|
|
31
|
+
pattern?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Generic term-highlighting rule.
|
|
34
|
+
* `label` controls CSS class (`cat-highlight-glossary-{label}`) and badge text.
|
|
35
|
+
* Examples: label='lexiqa', label='tb-target', label='search' */
|
|
36
|
+
interface IKeywordsRule {
|
|
37
|
+
type: 'glossary';
|
|
38
|
+
label: string;
|
|
39
|
+
entries: Array<IKeywordsEntry>;
|
|
40
|
+
}
|
|
41
|
+
/** @deprecated Use `IKeywordsEntry` instead. */
|
|
42
|
+
type IGlossaryEntry = IKeywordsEntry;
|
|
43
|
+
/** @deprecated Use `IKeywordsRule` instead. */
|
|
44
|
+
type IGlossaryRule = IKeywordsRule;
|
|
45
|
+
interface ISpecialCharEntry {
|
|
46
|
+
/** Human-readable name shown in the popover, e.g. "Non-Breaking Space" */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Regex that matches one occurrence of this character */
|
|
49
|
+
pattern: RegExp;
|
|
50
|
+
}
|
|
51
|
+
interface ISpecialCharRule {
|
|
52
|
+
type: 'special-char';
|
|
53
|
+
entries: Array<ISpecialCharEntry>;
|
|
54
|
+
}
|
|
55
|
+
interface ITagRule {
|
|
56
|
+
type: 'tag';
|
|
57
|
+
/** When true (default), innermost tag pairs are matched first. */
|
|
58
|
+
detectInner?: boolean;
|
|
59
|
+
/** When true, tags become atomic (other highlights inside them are
|
|
60
|
+
* suppressed) and the CSS collapsed rendering kicks in.
|
|
61
|
+
* When false/undefined, tags can be split by search/glossary highlights. */
|
|
62
|
+
collapsed?: boolean;
|
|
63
|
+
/** Which tags should be collapsed when `collapsed` is true.
|
|
64
|
+
* - `'all'` (default): collapse every matched tag/placeholder.
|
|
65
|
+
* - `'html-only'`: only collapse HTML-like tags (`<tag>`, `</tag>`, `<br/>`);
|
|
66
|
+
* non-HTML placeholders (e.g. `{{var}}`, `$amount`) stay expanded. */
|
|
67
|
+
collapseScope?: 'all' | 'html-only';
|
|
68
|
+
/** Custom regex pattern (source string) for matching tags / placeholders.
|
|
69
|
+
* When provided, every match is treated as a standalone tag token
|
|
70
|
+
* numbered sequentially (`<1>`, `<2>`, …).
|
|
71
|
+
* When omitted, the built-in HTML tag detection with open/close pairing is used.
|
|
72
|
+
* @example '<[^>]+>|(\\{\\{[^{}]*\\}\\})|(\\{[^{}]*\\})' */
|
|
73
|
+
pattern?: string;
|
|
74
|
+
}
|
|
75
|
+
interface IQuoteRuleMapping {
|
|
76
|
+
opening: string;
|
|
77
|
+
closing: string;
|
|
78
|
+
}
|
|
79
|
+
interface IQuoteRule {
|
|
80
|
+
type: 'quote';
|
|
81
|
+
singleQuote: IQuoteRuleMapping;
|
|
82
|
+
doubleQuote: IQuoteRuleMapping;
|
|
83
|
+
/** When `true`, quote replacement is also applied inside HTML tags
|
|
84
|
+
* (e.g. attribute values like `href="…"`). When `false` (the default),
|
|
85
|
+
* quotes that fall inside a tag range are suppressed. */
|
|
86
|
+
detectInTags?: boolean;
|
|
87
|
+
/** Options forwarded to the `detectQuotes()` utility.
|
|
88
|
+
* Allows customising contraction escaping, nesting behaviour, etc. */
|
|
89
|
+
detectOptions?: DetectQuotesOptions;
|
|
90
|
+
}
|
|
91
|
+
interface ILinkRule {
|
|
92
|
+
type: 'link';
|
|
93
|
+
/** Custom regex pattern (source string) for matching URLs.
|
|
94
|
+
* When omitted, a built-in pattern matching http/https URLs and
|
|
95
|
+
* www-prefixed domains is used. */
|
|
96
|
+
pattern?: string;
|
|
97
|
+
}
|
|
98
|
+
/** A user that can be mentioned via the @ trigger. */
|
|
99
|
+
interface IMentionUser {
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
/** Optional avatar render function. When omitted an initials fallback is shown. */
|
|
103
|
+
avatar?: () => React__default.ReactNode;
|
|
104
|
+
}
|
|
105
|
+
interface IMentionRule {
|
|
106
|
+
type: 'mention';
|
|
107
|
+
/** List of users available for the mention typeahead. */
|
|
108
|
+
users: Array<IMentionUser>;
|
|
109
|
+
/** Trigger character, default `@`. */
|
|
110
|
+
trigger?: string;
|
|
111
|
+
}
|
|
112
|
+
type MooRule = ISpellCheckRule | IKeywordsRule | ISpecialCharRule | ITagRule | IQuoteRule | ILinkRule | IMentionRule;
|
|
113
|
+
interface SpellCheckAnnotation {
|
|
114
|
+
type: 'spellcheck';
|
|
115
|
+
id: string;
|
|
116
|
+
data: ISpellCheckValidation;
|
|
117
|
+
}
|
|
118
|
+
interface KeywordsAnnotation {
|
|
119
|
+
type: 'glossary';
|
|
120
|
+
id: string;
|
|
121
|
+
data: {
|
|
122
|
+
label: string;
|
|
123
|
+
term: string;
|
|
124
|
+
description?: string;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** @deprecated Use `KeywordsAnnotation` instead. */
|
|
128
|
+
type GlossaryAnnotation = KeywordsAnnotation;
|
|
129
|
+
interface SpecialCharAnnotation {
|
|
130
|
+
type: 'special-char';
|
|
131
|
+
id: string;
|
|
132
|
+
data: {
|
|
133
|
+
name: string;
|
|
134
|
+
char: string;
|
|
135
|
+
codePoint: string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
interface TagAnnotation {
|
|
139
|
+
type: 'tag';
|
|
140
|
+
id: string;
|
|
141
|
+
data: {
|
|
142
|
+
tagNumber: number;
|
|
143
|
+
tagName: string;
|
|
144
|
+
isClosing: boolean;
|
|
145
|
+
isSelfClosing: boolean;
|
|
146
|
+
originalText: string;
|
|
147
|
+
displayText: string;
|
|
148
|
+
/** `true` when the tag was classified as HTML (`<tag>`, `</tag>`, `<br/>`). */
|
|
149
|
+
isHtml: boolean;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
interface QuoteAnnotation {
|
|
153
|
+
type: 'quote';
|
|
154
|
+
id: string;
|
|
155
|
+
data: {
|
|
156
|
+
quoteType: 'single' | 'double';
|
|
157
|
+
position: 'opening' | 'closing';
|
|
158
|
+
originalChar: string;
|
|
159
|
+
replacementChar: string;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
interface LinkAnnotation {
|
|
163
|
+
type: 'link';
|
|
164
|
+
id: string;
|
|
165
|
+
data: {
|
|
166
|
+
url: string;
|
|
167
|
+
displayText: string;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
type RuleAnnotation = SpellCheckAnnotation | KeywordsAnnotation | SpecialCharAnnotation | TagAnnotation | QuoteAnnotation | LinkAnnotation;
|
|
171
|
+
interface RawRange {
|
|
172
|
+
start: number;
|
|
173
|
+
end: number;
|
|
174
|
+
annotation: RuleAnnotation;
|
|
175
|
+
}
|
|
176
|
+
interface HighlightSegment {
|
|
177
|
+
start: number;
|
|
178
|
+
end: number;
|
|
179
|
+
annotations: Array<RuleAnnotation>;
|
|
180
|
+
}
|
|
181
|
+
interface PopoverState {
|
|
182
|
+
visible: boolean;
|
|
183
|
+
x: number;
|
|
184
|
+
y: number;
|
|
185
|
+
/** Full bounding rect of the hovered highlight element.
|
|
186
|
+
* Used by Popper so that flip/shift avoid overlapping the target. */
|
|
187
|
+
anchorRect?: {
|
|
188
|
+
top: number;
|
|
189
|
+
left: number;
|
|
190
|
+
bottom: number;
|
|
191
|
+
right: number;
|
|
192
|
+
width: number;
|
|
193
|
+
height: number;
|
|
194
|
+
};
|
|
195
|
+
ruleIds: Array<string>;
|
|
196
|
+
}
|
|
197
|
+
/** Props passed to the custom popover content renderer.
|
|
198
|
+
* Use this to fully replace the built-in popover UI for any annotation. */
|
|
199
|
+
interface PopoverContentRendererProps {
|
|
200
|
+
annotation: RuleAnnotation;
|
|
201
|
+
/** Call with a suggestion string to apply it (only relevant for spellcheck) */
|
|
202
|
+
onSuggestionClick: (suggestion: string) => void;
|
|
203
|
+
}
|
|
204
|
+
/** Render function for custom popover content.
|
|
205
|
+
* Return `undefined` / `null` to fall back to the default built-in renderer. */
|
|
206
|
+
type PopoverContentRenderer = (props: PopoverContentRendererProps) => React__default.ReactNode;
|
|
207
|
+
/** Imperative API exposed via `ref` on `<CATEditor>`. */
|
|
208
|
+
interface CATEditorRef {
|
|
209
|
+
/** Insert text at the current (or last saved) cursor position. */
|
|
210
|
+
insertText: (text: string) => void;
|
|
211
|
+
/** Focus the editor. */
|
|
212
|
+
focus: () => void;
|
|
213
|
+
/** Get the full plain-text content. */
|
|
214
|
+
getText: () => string;
|
|
215
|
+
/** Replace all occurrences of `search` with `replacement` in the editor
|
|
216
|
+
* content. Returns the number of replacements made. */
|
|
217
|
+
replaceAll: (search: string, replacement: string) => number;
|
|
218
|
+
/** Temporarily highlight editor elements matching `annotationId` with a
|
|
219
|
+
* pink "flash" overlay. The highlight is automatically removed after
|
|
220
|
+
* `durationMs` (default 5 000 ms), when the user edits the text, or
|
|
221
|
+
* when `clearFlash` / another `flashHighlight` call is made. */
|
|
222
|
+
flashHighlight: (annotationId: string, durationMs?: number) => void;
|
|
223
|
+
/** Remove any active flash highlight immediately. */
|
|
224
|
+
clearFlash: () => void;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
type SerializedMentionNode = Spread<{
|
|
228
|
+
mentionId: string;
|
|
229
|
+
mentionName: string;
|
|
230
|
+
}, SerializedTextNode>;
|
|
231
|
+
/** Render function that fills the DOM of a mention node.
|
|
232
|
+
* Receives the host `<span>` element, the mentionId, and the display name.
|
|
233
|
+
* Return `true` to signal that you handled the rendering;
|
|
234
|
+
* return `false` / `undefined` to fall back to the built-in renderer. */
|
|
235
|
+
type MentionDOMRenderer = (element: HTMLSpanElement, mentionId: string, mentionName: string) => boolean | void;
|
|
236
|
+
interface MentionNodeConfig {
|
|
237
|
+
/** Custom renderer for the mention node DOM content.
|
|
238
|
+
* When provided, this function is called every time the mention DOM
|
|
239
|
+
* is created or updated. Return `true` to take over rendering. */
|
|
240
|
+
renderDOM?: MentionDOMRenderer;
|
|
241
|
+
/** Converts a mention ID to the model text stored in the editor.
|
|
242
|
+
* Default: `` id => `@{${id}}` `` — produces `@{5}`, `@{user_abc}`, etc. */
|
|
243
|
+
serialize?: (id: string) => string;
|
|
244
|
+
/** RegExp used to detect mention patterns in pasted / imported text.
|
|
245
|
+
* Must contain exactly one capture group that extracts the mention ID.
|
|
246
|
+
* The `g` flag is required.
|
|
247
|
+
* Default: `/@\{([^}]+)\}/g` — matches `@{5}`, `@{user_abc}`, etc. */
|
|
248
|
+
pattern?: RegExp;
|
|
249
|
+
}
|
|
250
|
+
/** Configure the global MentionNode behaviour.
|
|
251
|
+
* Call this from CATEditor whenever props change. */
|
|
252
|
+
declare function setMentionNodeConfig(config: MentionNodeConfig): void;
|
|
253
|
+
/** Get the model text for a mention with the given ID,
|
|
254
|
+
* using the configured `serialize` function (or the default `@{id}`). */
|
|
255
|
+
declare function getMentionModelText(id: string): string;
|
|
256
|
+
/** Get a fresh copy of the mention detection RegExp.
|
|
257
|
+
* A new RegExp is returned every time so that `lastIndex` is always 0. */
|
|
258
|
+
declare function getMentionPattern(): RegExp;
|
|
259
|
+
/**
|
|
260
|
+
* A custom Lexical TextNode that represents a mention.
|
|
261
|
+
*
|
|
262
|
+
* **Model text** (what Lexical stores, what `getTextContent()` returns):
|
|
263
|
+
* `@{mentionId}` — e.g. `@1`, `@user_abc`.
|
|
264
|
+
*
|
|
265
|
+
* **Visible DOM**: `@DisplayName` (+ optional avatar). The display is
|
|
266
|
+
* controlled by a configurable renderer — see `setMentionNodeConfig`.
|
|
267
|
+
*
|
|
268
|
+
* The node uses "segmented" mode so it behaves as an atomic unit:
|
|
269
|
+
* backspace removes the whole mention, typing at boundaries creates a
|
|
270
|
+
* sibling TextNode instead of editing the mention text.
|
|
271
|
+
*/
|
|
272
|
+
declare class MentionNode extends TextNode {
|
|
273
|
+
__mentionId: string;
|
|
274
|
+
__mentionName: string;
|
|
275
|
+
static getType(): string;
|
|
276
|
+
static clone(node: MentionNode): MentionNode;
|
|
277
|
+
static importJSON(serializedNode: SerializedMentionNode): MentionNode;
|
|
278
|
+
constructor(mentionId: string, mentionName: string, text?: string, key?: NodeKey);
|
|
279
|
+
exportJSON(): SerializedMentionNode;
|
|
280
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
281
|
+
updateDOM(prevNode: MentionNode, dom: HTMLElement, _config: EditorConfig): boolean;
|
|
282
|
+
/** Fill the span with visible content (default: @label, or custom). */
|
|
283
|
+
private _renderInnerDOM;
|
|
284
|
+
exportDOM(): DOMExportOutput;
|
|
285
|
+
static importDOM(): DOMConversionMap | null;
|
|
286
|
+
isTextEntity(): true;
|
|
287
|
+
canInsertTextBefore(): boolean;
|
|
288
|
+
canInsertTextAfter(): boolean;
|
|
289
|
+
}
|
|
290
|
+
declare function $createMentionNode(mentionId: string, mentionName: string, textContent?: string): MentionNode;
|
|
291
|
+
declare function $isMentionNode(node: LexicalNode | null | undefined): node is MentionNode;
|
|
292
|
+
|
|
293
|
+
interface CATEditorProps {
|
|
294
|
+
/** Initial text content for the editor */
|
|
295
|
+
initialText?: string;
|
|
296
|
+
/** Rules to apply for highlighting */
|
|
297
|
+
rules?: Array<MooRule>;
|
|
298
|
+
/** Called when editor content changes */
|
|
299
|
+
onChange?: (text: string) => void;
|
|
300
|
+
/** Called when a suggestion is applied.
|
|
301
|
+
* Provides the ruleId, the replacement text, the original text
|
|
302
|
+
* span (start / end / content), and the ruleType so consumers
|
|
303
|
+
* can identify which rule triggered the replacement. */
|
|
304
|
+
onSuggestionApply?: (ruleId: string, suggestion: string, range: {
|
|
305
|
+
start: number;
|
|
306
|
+
end: number;
|
|
307
|
+
content: string;
|
|
308
|
+
}, ruleType: RuleAnnotation['type']) => void;
|
|
309
|
+
/** Custom code-point → display symbol map. Merged on top of the
|
|
310
|
+
* built-in `CODEPOINT_DISPLAY_MAP`. Pass `{ 0x00a0: '⍽' }` to
|
|
311
|
+
* override the symbol shown for non-breaking spaces, etc. */
|
|
312
|
+
codepointDisplayMap?: Record<number, string>;
|
|
313
|
+
/** Custom renderer for popover content per annotation.
|
|
314
|
+
* Return `null`/`undefined` to use the built-in default. */
|
|
315
|
+
renderPopoverContent?: PopoverContentRenderer;
|
|
316
|
+
/** Called when a link highlight is clicked. If not provided, links
|
|
317
|
+
* open in a new browser tab via `window.open`. */
|
|
318
|
+
onLinkClick?: (url: string) => void;
|
|
319
|
+
/** Whether clicking a link highlight should open the URL.
|
|
320
|
+
* When `false`, link clicks are ignored (the click positions the
|
|
321
|
+
* cursor in the editor text instead). Default: `true`. */
|
|
322
|
+
openLinksOnClick?: boolean;
|
|
323
|
+
/** Called when a mention node is clicked. */
|
|
324
|
+
onMentionClick?: (userId: string, userName: string) => void;
|
|
325
|
+
/** Called when a mention is inserted via the typeahead. */
|
|
326
|
+
onMentionInsert?: (user: IMentionUser) => void;
|
|
327
|
+
/** Converts a mention ID to model text.
|
|
328
|
+
* Default: `` id => `@{${id}}` `` producing `@{5}`, `@{user_abc}`, etc. */
|
|
329
|
+
mentionSerialize?: (id: string) => string;
|
|
330
|
+
/** RegExp to detect mention patterns in pasted / imported text.
|
|
331
|
+
* Must have one capture group for the ID and use the `g` flag.
|
|
332
|
+
* Default: `/@\{([^}]+)\}/g` */
|
|
333
|
+
mentionPattern?: RegExp;
|
|
334
|
+
/** Custom DOM renderer for mention nodes.
|
|
335
|
+
* Receives the `<span>` host element, the mentionId and the
|
|
336
|
+
* display name. Return `true` to take over rendering;
|
|
337
|
+
* return `false`/`undefined` to use the default `@name` label. */
|
|
338
|
+
renderMentionDOM?: MentionDOMRenderer;
|
|
339
|
+
/** Placeholder text */
|
|
340
|
+
placeholder?: string;
|
|
341
|
+
/** Additional class name for the editor container */
|
|
342
|
+
className?: string;
|
|
343
|
+
/** Text direction. `'ltr'` (default), `'rtl'`, or `'auto'`. */
|
|
344
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
345
|
+
/** Text direction for the highlight popover. Defaults to `'ltr'` so
|
|
346
|
+
* that popover content stays left-to-right even when the editor is
|
|
347
|
+
* RTL. Set to `'rtl'` or `'auto'` if your popover content should
|
|
348
|
+
* follow the editor direction, or pass `'inherit'` to use the same
|
|
349
|
+
* direction as the editor (`dir` prop). */
|
|
350
|
+
popoverDir?: 'ltr' | 'rtl' | 'auto' | 'inherit';
|
|
351
|
+
/** When `true`, applies a Japanese-optimised font stack to the editor. */
|
|
352
|
+
jpFont?: boolean;
|
|
353
|
+
/** Whether the editor content is editable. Default: `true`.
|
|
354
|
+
* When `false`, all text mutations are blocked.
|
|
355
|
+
* @see readOnlySelectable */
|
|
356
|
+
editable?: boolean;
|
|
357
|
+
/** When `editable` is `false` and this is `true`, the editor still
|
|
358
|
+
* allows caret placement, range selection and copy — but rejects
|
|
359
|
+
* any content changes. Default: `false`. */
|
|
360
|
+
readOnlySelectable?: boolean;
|
|
361
|
+
/** Custom keydown handler. Called before Lexical processes the event.
|
|
362
|
+
* Return `true` to prevent Lexical (and the browser) from handling
|
|
363
|
+
* the key. Useful for intercepting Enter, Tab, Escape, etc.
|
|
364
|
+
* @example
|
|
365
|
+
* ```tsx
|
|
366
|
+
* onKeyDown={(e) => {
|
|
367
|
+
* if (e.key === 'Enter' && !e.shiftKey) {
|
|
368
|
+
* e.preventDefault()
|
|
369
|
+
* handleSubmit()
|
|
370
|
+
* return true
|
|
371
|
+
* }
|
|
372
|
+
* return false
|
|
373
|
+
* }}
|
|
374
|
+
* ``` */
|
|
375
|
+
onKeyDown?: (event: KeyboardEvent) => boolean;
|
|
376
|
+
/** @deprecated Use `editable` instead. */
|
|
377
|
+
readOnly?: boolean;
|
|
378
|
+
}
|
|
379
|
+
declare const CATEditor: React.ForwardRefExoticComponent<CATEditorProps & React.RefAttributes<CATEditorRef>>;
|
|
380
|
+
|
|
381
|
+
export { $createMentionNode as $, getMentionPattern as A, setMentionNodeConfig as B, CATEditor as C, type GlossaryAnnotation as G, type HighlightSegment as H, type IKeywordsRule as I, type KeywordsAnnotation as K, type LinkAnnotation as L, type MooRule as M, type PopoverContentRenderer as P, type QuoteAnnotation as Q, type RuleAnnotation as R, type SerializedMentionNode as S, type TagAnnotation as T, type CATEditorProps as a, type CATEditorRef as b, type ILinkRule as c, type IMentionRule as d, type IMentionUser as e, type IQuoteRule as f, type ISpecialCharRule as g, type ISpellCheckRule as h, type ITagRule as i, type PopoverContentRendererProps as j, $isMentionNode as k, type IGlossaryEntry as l, type IGlossaryRule as m, type IKeywordsEntry as n, type IQuoteRuleMapping as o, type ISpecialCharEntry as p, type ISpellCheckValidation as q, type ISuggestion as r, type MentionDOMRenderer as s, MentionNode as t, type MentionNodeConfig as u, type PopoverState as v, type RawRange as w, type SpecialCharAnnotation as x, type SpellCheckAnnotation as y, getMentionModelText as z };
|