@rcarls/rc-textarea 0.5.0 → 0.7.0

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +67 -31
  3. package/dist/custom-elements.json +637 -41
  4. package/dist/folding-action-controller-sxM3IomW.js +91 -0
  5. package/dist/folding-action-controller-sxM3IomW.js.map +1 -0
  6. package/dist/{line-actions-controller-CB8U0WvH.js → line-actions-controller-DUdXQxMA.js} +912 -785
  7. package/dist/line-actions-controller-DUdXQxMA.js.map +1 -0
  8. package/dist/rc-textarea-define.js +2 -2
  9. package/dist/rc-textarea-folding.js +5 -0
  10. package/dist/rc-textarea-folding.js.map +1 -0
  11. package/dist/rc-textarea.js +1 -1
  12. package/dist/types/packages/rc-textarea/src/blots.d.ts +1 -1
  13. package/dist/types/packages/rc-textarea/src/decoration.d.ts +3 -2
  14. package/dist/types/packages/rc-textarea/src/document.d.ts +2 -1
  15. package/dist/types/packages/rc-textarea/src/folding-action-controller.d.ts +46 -0
  16. package/dist/types/packages/rc-textarea/src/folding.d.ts +2 -0
  17. package/dist/types/packages/rc-textarea/src/index.d.ts +7 -7
  18. package/dist/types/packages/rc-textarea/src/line-actions-controller.d.ts +5 -4
  19. package/dist/types/packages/rc-textarea/src/line-decorator.d.ts +1 -1
  20. package/dist/types/packages/rc-textarea/src/pattern-matcher.d.ts +1 -1
  21. package/dist/types/packages/rc-textarea/src/rc-textarea.d.ts +123 -20
  22. package/dist/types/packages/rc-textarea/src/selection.d.ts +2 -0
  23. package/dist/types/packages/rc-textarea/src/types.d.ts +52 -9
  24. package/package.json +9 -3
  25. package/dist/line-actions-controller-CB8U0WvH.js.map +0 -1
  26. package/dist/types/packages/rc-textarea/src/decoration.unit.test.d.ts +0 -1
  27. package/dist/types/packages/rc-textarea/src/line-decorator.test.d.ts +0 -1
  28. package/dist/types/packages/rc-textarea/src/pattern-matcher.unit.test.d.ts +0 -1
  29. package/dist/types/packages/rc-textarea/src/rc-textarea.test.d.ts +0 -0
  30. package/dist/types/packages/rc-textarea/src/test-helpers.d.ts +0 -20
@@ -1,5 +1,5 @@
1
- import { R as e } from "./line-actions-controller-CB8U0WvH.js";
2
- import { L as o, c as s, m as c } from "./line-actions-controller-CB8U0WvH.js";
1
+ import { R as e } from "./line-actions-controller-DUdXQxMA.js";
2
+ import { L as o, c as s, m as c } from "./line-actions-controller-DUdXQxMA.js";
3
3
  customElements.get("rc-textarea") || customElements.define("rc-textarea", e);
4
4
  export {
5
5
  o as LineActionsController,
@@ -0,0 +1,5 @@
1
+ import { F as l } from "./folding-action-controller-sxM3IomW.js";
2
+ export {
3
+ l as FoldingActionController
4
+ };
5
+ //# sourceMappingURL=rc-textarea-folding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-textarea-folding.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,4 +1,4 @@
1
- import { L as t, R as r, c as o, m as s } from "./line-actions-controller-CB8U0WvH.js";
1
+ import { L as t, R as r, c as o, m as s } from "./line-actions-controller-DUdXQxMA.js";
2
2
  export {
3
3
  t as LineActionsController,
4
4
  r as RCTextarea,
@@ -1,5 +1,5 @@
1
1
  import { Registry, ScrollBlot, BlockBlot, InlineBlot, EmbedBlot } from 'parchment';
2
- import { MarkDecoration } from './types.ts';
2
+ import { MarkDecoration } from './types.js';
3
3
  export { Registry };
4
4
  export declare class RCScrollBlot extends ScrollBlot {
5
5
  static blotName: string;
@@ -1,5 +1,5 @@
1
- import { Decoration, MarkDecoration, LineDecoration, DecorationInput } from './types.ts';
2
- export { generateId } from './types.ts';
1
+ import { Decoration, MarkDecoration, LineDecoration, DecorationInput } from './types.js';
2
+ export { generateId } from './types.js';
3
3
  /**
4
4
  * The minimal description of a single contiguous edit:
5
5
  * a deletion of `removed` characters at `start`, followed by an insertion of
@@ -32,6 +32,7 @@ export declare function findEdit(oldValue: string, newValue: string): Edit;
32
32
  * - `LineDecoration`: dropped if its line falls within the deleted region;
33
33
  * otherwise shifted by the line delta.
34
34
  * - `WidgetDecoration`: treated like a zero-width mark at its offset.
35
+ * - `FoldDecoration`: dropped; the owning plugin recalculates source ranges.
35
36
  *
36
37
  * Returns a new array. The input array and its elements are not mutated.
37
38
  */
@@ -1,6 +1,7 @@
1
- import { Decoration } from './types.ts';
1
+ import { Decoration } from './types.js';
2
2
  export declare class RCDocument {
3
3
  private readonly scroll;
4
+ private readonly lineSignatures;
4
5
  constructor($documentRoot: HTMLDivElement);
5
6
  /**
6
7
  * Rebuild the document tree.
@@ -0,0 +1,46 @@
1
+ import { DecorationInput, RCTextareaPluginAPI } from './types.js';
2
+ export interface SourceFoldRange {
3
+ /** First source line hidden by the fold (one-based, inclusive). */
4
+ fromLine: number;
5
+ /** Last source line hidden by the fold (one-based, inclusive). */
6
+ toLine: number;
7
+ }
8
+ export interface FoldingAction {
9
+ /** Stable identity for the expansion state across plugin updates. */
10
+ key: string;
11
+ /** Name the region and its context; aria-expanded supplies its current state. */
12
+ label: string;
13
+ /** Source lines or a caller-owned panel. The caller renders panel widgets separately. */
14
+ target: SourceFoldRange | HTMLElement;
15
+ /** One-based gutter line. Omit to place the button in the trailing lane. */
16
+ gutterLine?: number;
17
+ /** Source offset for a trailing-lane button; required without gutterLine. */
18
+ offset?: number;
19
+ /** Initial state for a newly seen key. Defaults to true. */
20
+ initiallyExpanded?: boolean;
21
+ /** Add decorative content beside the gutter button. */
22
+ createGutterContent?: ($button: HTMLButtonElement) => HTMLElement;
23
+ /** Refresh the cached gutter content without replacing its focused button. */
24
+ updateGutterContent?: ($content: HTMLElement) => void;
25
+ }
26
+ export interface FoldingActionControllerOptions {
27
+ /** Render a decorative icon for the current expansion state. */
28
+ createTriggerIcon?: (expanded: boolean) => Element | null;
29
+ }
30
+ /** Reconcile independent, accessible folding actions without owning their content. */
31
+ export declare class FoldingActionController {
32
+ private readonly entries;
33
+ private readonly gutterGroupElements;
34
+ private readonly api;
35
+ private readonly options;
36
+ constructor(api: RCTextareaPluginAPI, options?: FoldingActionControllerOptions);
37
+ private updateIcon;
38
+ private applyState;
39
+ /** Return trigger and collapsed-range decorations; callers provide any panel widget. */
40
+ getDecorations(actions: readonly FoldingAction[]): DecorationInput[];
41
+ /** Get expansion state for an existing key. */
42
+ isExpanded(key: string): boolean | undefined;
43
+ /** Discard expansion state and return focus before removing a focused control. */
44
+ clear(): void;
45
+ private release;
46
+ }
@@ -0,0 +1,2 @@
1
+ export { FoldingActionController } from './folding-action-controller.js';
2
+ export type { FoldingAction, FoldingActionControllerOptions, SourceFoldRange, } from './folding-action-controller.js';
@@ -1,7 +1,7 @@
1
- export { RCTextarea } from './rc-textarea.ts';
2
- export type { Decoration, DecorationInput, MarkDecoration, MarkDecorationStyle, LineDecoration, WidgetDecoration, RCTextareaPlugin, RCTextareaPluginAPI, TextPattern, LineDecoratorPlugin, Token, } from './types.ts';
3
- export { matchPatternResults } from './pattern-matcher.ts';
4
- export { createLineDecoratorPlugin } from './line-decorator.ts';
5
- export type { LineDecoratorPluginOptions } from './line-decorator.ts';
6
- export { LineActionsController } from './line-actions-controller.ts';
7
- export type { LineAction, LineActionsOptions } from './line-actions-controller.ts';
1
+ export { RCTextarea } from './rc-textarea.js';
2
+ export type { Decoration, DecorationInput, MarkDecoration, MarkDecorationStyle, LineDecoration, WidgetDecoration, FoldDecoration, RCTextareaPlugin, RCTextareaPluginAPI, TextPattern, LineDecoratorPlugin, Token, } from './types.js';
3
+ export { matchPatternResults } from './pattern-matcher.js';
4
+ export { createLineDecoratorPlugin } from './line-decorator.js';
5
+ export type { LineDecoratorPluginOptions } from './line-decorator.js';
6
+ export { LineActionsController } from './line-actions-controller.js';
7
+ export type { LineAction, LineActionsOptions } from './line-actions-controller.js';
@@ -1,4 +1,4 @@
1
- import { RCTextareaPluginAPI, DecorationInput } from './types.ts';
1
+ import { RCTextareaPluginAPI, DecorationInput } from './types.js';
2
2
  export interface LineAction {
3
3
  /** Stable identifier used for action-set change detection. */
4
4
  id: string;
@@ -13,8 +13,9 @@ export interface LineActionsOptions {
13
13
  /** Where to place the inline widget. Default: `'append'`. */
14
14
  position?: 'append';
15
15
  /**
16
- * Create and return a DOM element for the given icon name (e.g. `'mdi-lock-outline'`).
17
- * Called when `action.iconName` is set. Return `null` to fall back to text label.
16
+ * Create and return a DOM element for the given icon name (e.g. `'mdi-lock-outline'`),
17
+ * such as an `<svg>` or an icon custom element. Called when `action.iconName` is set.
18
+ * Return `null` to fall back to text label.
18
19
  *
19
20
  * @example
20
21
  * ```ts
@@ -25,7 +26,7 @@ export interface LineActionsOptions {
25
26
  * }
26
27
  * ```
27
28
  */
28
- createIcon?: (iconName: string) => HTMLElement | null;
29
+ createIcon?: (iconName: string) => Element | null;
29
30
  /**
30
31
  * Full control over button content. Called instead of the default icon/label
31
32
  * logic. Use `createIcon` when you only need to swap the icon renderer —
@@ -1,4 +1,4 @@
1
- import { DecorationInput, LineDecoratorPlugin, RCTextareaPlugin } from './types.ts';
1
+ import { DecorationInput, LineDecoratorPlugin, RCTextareaPlugin } from './types.js';
2
2
  export interface LineDecoratorPluginOptions {
3
3
  /**
4
4
  * An array of subscriber setup functions for (e.g., reactive
@@ -1,4 +1,4 @@
1
- import { TextPattern, MarkDecoration, LineDecoration } from './types.ts';
1
+ import { TextPattern, MarkDecoration, LineDecoration } from './types.js';
2
2
  export type { TextPattern };
3
3
  /**
4
4
  * Run all `patterns` against `value` and collect every match as decorations.
@@ -1,8 +1,8 @@
1
1
  import { LitElement, CSSResultGroup } from 'lit';
2
- import { NativeChildController } from '@rcarls/rc-common';
3
- import { RCDocument } from './document.ts';
4
- import { SavedSelection } from './selection.ts';
5
- import { Decoration, DecorationInput, RCTextareaPlugin, RCTextareaPluginAPI, TextPattern } from './types.ts';
2
+ import { FlowController, LocaleController, NativeChildController } from '@rcarls/rc-common';
3
+ import { RCDocument } from './document.js';
4
+ import { SavedSelection } from './selection.js';
5
+ import { Decoration, DecorationInput, RCTextareaPlugin, RCTextareaPluginAPI, TextPattern, FoldDecoration } from './types.js';
6
6
  declare global {
7
7
  interface HTMLElementTagNameMap {
8
8
  'rc-textarea': RCTextarea;
@@ -13,8 +13,14 @@ interface UndoEntry {
13
13
  anchorOffset: number;
14
14
  focusOffset: number;
15
15
  }
16
+ interface FoldBounds {
17
+ from: number;
18
+ to: number;
19
+ anchorEnd: number;
20
+ reveal: () => void;
21
+ }
16
22
  /**
17
- * Textarea wrapper with line decorations, gutter rendering, inline widgets, and plugin hooks.
23
+ * Textarea wrapper with line decorations, gutter rendering, folding actions, and plugin hooks.
18
24
  *
19
25
  * Provide a native `<textarea>` as the direct child when the component is
20
26
  * editable. Read-only displays may be driven from `value` or `defaultValue`.
@@ -38,14 +44,23 @@ interface UndoEntry {
38
44
  * });
39
45
  * ```
40
46
  *
47
+ * The slotted `<textarea>` describes the content: its `lang` and `spellcheck` are
48
+ * mirrored onto the editor, and its `dir` sets the editor's base direction (`auto`
49
+ * resolves from the first strong character of the value, else the host's direction).
50
+ * The host's own `lang` stays the interface language, which formats line numbers.
51
+ *
41
52
  * @slot - Accepts a native `<textarea>` element for form wiring.
42
53
  *
43
54
  * @fires rc-textarea-change - Fired when the field value changes
55
+ * @fires rc-textarea-focus - Fired when the field gains focus
44
56
  * @fires rc-textarea-blur - Fired when the field loses focus
57
+ * @fires rc-textarea-select - Fired when the selection changes while the editor is focused
45
58
  *
46
59
  * @csspart root - The outer flex container wrapping the gutter and editor area.
47
60
  * @csspart gutter - The gutter column container.
48
61
  * @csspart gutter-cells - The gutter's line-label list.
62
+ * @csspart fold-trigger - Native button created by a folding action; style its visual appearance without changing its target or keyboard behavior.
63
+ * @csspart fold-icon - Consumer-supplied icon inside a folding action button, when provided.
49
64
  * @csspart editor-area - The container positioning the editor over the slotted textarea.
50
65
  * @csspart editor - The contenteditable field surface.
51
66
  *
@@ -53,26 +68,46 @@ interface UndoEntry {
53
68
  * @attr gutter - Enable the gutter column without any built-in content.
54
69
  * @attr word-wrap - Wrap long lines within the field to prevent horizontal overflow.
55
70
  * @attr auto-grow - Allow the field to grow vertically with content to prevent vertical overflow.
56
- * @attr read-only - Disable editing. The field renders as a styled read-only display.
71
+ * @attr line-direction - `base` (default) gives every line the editor's base direction; `auto` gives
72
+ * each line the direction of its first strong character, holding lines without one to the base.
73
+ * @attr [data-readonly] - Reflected while the slotted `<textarea>` is `readonly`. The editor stays
74
+ * focusable and selectable, announces `aria-readonly`, and blocks edits.
57
75
  * @attr value - Current field value.
58
76
  * @attr default-value - Initial uncontrolled value.
59
77
  *
60
78
  * @cssprop [--rc-textarea-border=1px solid ButtonBorder] - Border around the field
61
79
  * @cssprop [--rc-textarea-border-radius=2px] - Border radius of the field
62
80
  * @cssprop [--rc-textarea-background=Field] - Background color of the field
63
- * @cssprop [--rc-textarea-color=FieldText] - Text color; falls back through --rc-text
81
+ * @cssprop [--rc-textarea-color=var(--rc-field-text, FieldText)] - Text color
64
82
  * @cssprop [--rc-textarea-font-family=monospace] - Font family
65
83
  * @cssprop [--rc-textarea-font-size=1em] - Font size
66
84
  * @cssprop [--rc-textarea-line-height=1.5] - Line height
67
- * @cssprop [--rc-textarea-padding=0.5em] - Padding inside the field area
85
+ * @cssprop [--rc-textarea-padding-block=0.5em] - Block padding of the editor and gutter; any `padding-block` value
86
+ * @cssprop [--rc-textarea-padding-inline=0.5em] - Inline padding on both sides of the editor and before the gutter; one length, which the active line highlight bleeds through
87
+ * @cssprop [--rc-textarea-gutter-min-inline-size=24px] - Minimum width of the gutter, including its padding
88
+ * @cssprop [--rc-textarea-trailing-size=2.5em] - Reserved inline space and minimum block size for trailing controls
68
89
  * @cssprop [--rc-textarea-focus-outline=2px solid Highlight] - Focus ring outline
69
90
  * @cssprop [--rc-textarea-caret-color=FieldText] - Caret color
70
- * @cssprop [--rc-textarea-active-line-bg=transparent] - Active line highlight color
91
+ * @cssprop [--rc-textarea-active-line-bg=transparent] - Active line highlight color, including editor and gutter padding
71
92
  * @cssprop [--rc-textarea-gutter-bg=Canvas] - Gutter background color
72
93
  * @cssprop [--rc-textarea-gutter-color=GrayText] - Gutter text color
73
94
  * @cssprop [--rc-textarea-gutter-border=1px solid ButtonBorder] - Gutter right border
74
95
  * @cssprop [--rc-textarea-gutter-font-family=var(--rc-textarea-font-family, monospace)] - Gutter font family
96
+ * @cssprop [--rc-textarea-gutter-content-gap=0.25em] - Gap between a retained line number and custom gutter content
75
97
  * @cssprop [--rc-textarea-gutter-padding-inline-end=0.75em] - Space between gutter labels and text
98
+ * @cssprop [--rc-line-actions-gap=2px] - Gap between inline line actions; popover actions default to 4px
99
+ * @cssprop [--rc-line-actions-margin-start=6px] - Inline-start margin before line actions
100
+ * @cssprop [--rc-line-action-padding=1px] - Inline line-action padding; popover actions default to 4px 6px
101
+ * @cssprop [--rc-line-action-border=none] - Line-action border; the popover surface defaults to 1px solid ButtonBorder
102
+ * @cssprop [--rc-line-action-border-radius=3px] - Line-action border radius; the popover surface defaults to 4px
103
+ * @cssprop [--rc-line-action-bg=transparent] - Line-action background; the popover surface defaults to Canvas
104
+ * @cssprop [--rc-line-action-color=inherit] - Line-action color; popover actions default to ButtonText
105
+ * @cssprop [--rc-line-action-font-size=1em] - Inline line-action font size; popover actions default to 0.85em
106
+ * @cssprop [--rc-line-action-opacity=0.4] - Resting opacity of inline line actions
107
+ * @cssprop [--rc-line-action-hover-opacity=1] - Hover and focus opacity of inline line actions
108
+ * @cssprop [--rc-line-action-hover-bg=transparent] - Hover and focus background; popover actions default to ButtonFace
109
+ * @cssprop [--rc-line-action-hover-color=inherit] - Hover and focus text color
110
+ * @cssprop [--rc-line-action-shadow=0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)] - Line-action popover shadow
76
111
  */
77
112
  export declare class RCTextarea extends LitElement {
78
113
  static styles: CSSResultGroup;
@@ -97,8 +132,20 @@ export declare class RCTextarea extends LitElement {
97
132
  wordWrap: boolean;
98
133
  /** Allow the field to grow vertically with content to prevent vertical overflow. */
99
134
  autoGrow: boolean;
100
- /** Disable editing. The field renders as a styled read-only display. */
101
- readOnly: boolean;
135
+ /**
136
+ * How each line's direction is chosen. `base` gives every line the editor's
137
+ * base direction, which suits code. `auto` gives a line with a letter the
138
+ * direction of its first strong character, for prose that mixes scripts, and
139
+ * holds lines without one (empty, digits, punctuation) to the base direction.
140
+ */
141
+ lineDirection: 'auto' | 'base';
142
+ /**
143
+ * Whether the slotted `<textarea>` is `readonly`. The native control is the
144
+ * source of truth; set `readonly` on it to change this state.
145
+ */
146
+ get readOnly(): boolean;
147
+ private _readOnly;
148
+ private _textareaAttributeObserver;
102
149
  /** Declarative plugin hook for framework integrations. */
103
150
  get plugin(): RCTextareaPlugin | null;
104
151
  /** Declarative plugin hook for framework integrations. */
@@ -130,6 +177,9 @@ export declare class RCTextarea extends LitElement {
130
177
  protected _document: RCDocument | null;
131
178
  protected _$textareaRef: WeakRef<HTMLTextAreaElement> | null;
132
179
  protected readonly _textareaController: NativeChildController<HTMLTextAreaElement>;
180
+ protected readonly _flowController: FlowController;
181
+ /** The interface locale, which formats line numbers. */
182
+ protected readonly _localeController: LocaleController;
133
183
  /** Plugin-owned decorations */
134
184
  protected _pluginDecorations: Map<string, Decoration>;
135
185
  /** Pattern-generated decorations (rebuilt on each value change). */
@@ -148,6 +198,9 @@ export declare class RCTextarea extends LitElement {
148
198
  protected _savedSelection: SavedSelection | null;
149
199
  protected _rafHandle: number | null;
150
200
  protected _composing: boolean;
201
+ private _contentAttributesPendingRender;
202
+ /** Set by Escape: the next Tab moves focus instead of inserting `\t`. */
203
+ private _tabMovesFocus;
151
204
  protected _isRendering: boolean;
152
205
  /** The currently highlighted `.line` element (active line). */
153
206
  protected _$activeLine: HTMLElement | null;
@@ -158,8 +211,18 @@ export declare class RCTextarea extends LitElement {
158
211
  protected _undoStack: UndoEntry[];
159
212
  protected _undoIndex: number;
160
213
  protected _resizeObserver: ResizeObserver | null;
214
+ protected _gutterBlockAxis: 'x' | 'y' | null;
161
215
  /** Cached per-line gutter labels from the last render — reused by ResizeObserver. */
162
- protected _gutterLabels: (string | null)[];
216
+ protected _gutterLabels: (string | HTMLElement | null | {
217
+ number: string;
218
+ content: string | HTMLElement;
219
+ })[];
220
+ /** Gutter cells with explicitly accessible plugin controls. */
221
+ protected _accessibleGutterLines: Set<number>;
222
+ /** Collapsed source ranges from the most recent render. */
223
+ protected _foldedRanges: FoldDecoration[];
224
+ protected _foldBounds: FoldBounds[];
225
+ protected _hiddenGutterLines: Set<number>;
163
226
  /**
164
227
  * The current field value.
165
228
  *
@@ -176,18 +239,52 @@ export declare class RCTextarea extends LitElement {
176
239
  disconnectedCallback(): void;
177
240
  firstUpdated(): void;
178
241
  updated(changed: Map<string, unknown>): void;
179
- render(): import('lit').TemplateResult<1>;
242
+ private _observeTextarea;
243
+ /**
244
+ * Mirror the native textarea's content attributes onto the editor. `lang`
245
+ * picks glyphs, the spelling dictionary and word boundaries; `spellcheck`
246
+ * stays off unless the textarea sets it. The host's own `lang` is the
247
+ * interface language and is never copied.
248
+ */
249
+ protected _syncContentAttributes(): void;
250
+ /**
251
+ * Set the editor's base direction from the textarea's `dir`. `auto` reads
252
+ * the native control's directionality, including its LTR neutral fallback;
253
+ * without `dir` the editor inherits. Composition keeps the current direction
254
+ * so the IME session isn't disturbed.
255
+ */
256
+ protected _syncContentDirection(): void;
257
+ /** The textarea's `lang`, or `''` when it sets none. */
258
+ protected _contentLang(): string;
259
+ /** The editor's resolved base direction. */
260
+ protected _contentDirection(): 'ltr' | 'rtl';
261
+ /**
262
+ * Mark lines with no strong character so `line-direction="auto"` holds them
263
+ * to the base direction. Reads the model value, which composition text
264
+ * doesn't reach until `compositionend`, so a composing line keeps its mark.
265
+ */
266
+ protected _syncLineDirections(value: string): void;
267
+ /**
268
+ * Mirror the native textarea's `readonly` attribute: the editor stays a
269
+ * focusable, selectable textbox that announces `aria-readonly`, and edits
270
+ * are blocked by the existing input guards.
271
+ */
272
+ protected _syncReadOnly(): void;
273
+ render(): import('lit-html').TemplateResult<1>;
180
274
  protected _onSlotChange(): void;
181
275
  protected _setupTextarea($textarea: HTMLTextAreaElement | null, $previousTextarea?: HTMLTextAreaElement | null): void;
182
276
  protected _onTextareaInvalid: () => void;
183
277
  protected _bindEditorEvents($editor: HTMLElement): void;
184
278
  protected _onInputEvent: () => void;
185
279
  protected _onBeforeInput: (event: InputEvent) => void;
280
+ protected _revealFoldAtOffset(offset: number): boolean;
281
+ protected _revealFoldForEdit(inputType: string): boolean;
186
282
  protected _onInput(): void;
187
283
  protected _onSelectionChange: () => void;
188
284
  protected _updateActiveLine(): void;
189
285
  protected _updateActiveGutterCell(): void;
190
286
  protected _onKeyDown: (e: KeyboardEvent) => void;
287
+ protected _isWidgetEvent(event: Event): boolean;
191
288
  protected _insertText(text: string): void;
192
289
  /**
193
290
  * Wrap the current selection with `prefix` and `suffix`.
@@ -204,6 +301,7 @@ export declare class RCTextarea extends LitElement {
204
301
  * When the selection is collapsed this is equivalent to `insertText`.
205
302
  */
206
303
  replaceSelection(text: string): void;
304
+ protected _onCopy: (event: ClipboardEvent) => void;
207
305
  protected _onPaste: (e: ClipboardEvent) => void;
208
306
  /** Reset the undo/redo history. Call when the field receives entirely new content. */
209
307
  clearHistory(): void;
@@ -251,15 +349,17 @@ export declare class RCTextarea extends LitElement {
251
349
  * - `string` — the label to display
252
350
  * - `null` — render an empty cell
253
351
  */
254
- protected _computeGutterLabels(allDecorations: Decoration[], value?: string): (string | null)[];
352
+ protected _computeGutterLabels(allDecorations: Decoration[], value?: string): (string | HTMLElement | null | {
353
+ number: string;
354
+ content: string | HTMLElement;
355
+ })[];
255
356
  /**
256
- * Synchronize the pixel height of each gutter cell to match the
357
+ * Synchronize the block size of each gutter cell to match the
257
358
  * corresponding `.line` element in the editor.
258
359
  *
259
- * In non-word-wrap mode the gutter uses a uniform `line-height` mirrored
260
- * from the editor's first line. In word-wrap mode each cell gets an explicit
261
- * `height` so wrapped lines stay vertically aligned with their gutter label.
262
- * Also copies the editor's computed `paddingTop`/`paddingBottom` to the
360
+ * In non-word-wrap mode without block widgets the gutter uses a uniform
361
+ * line height. Wrapped lines and block widgets use per-line heights instead.
362
+ * Also copies the editor's computed block padding to the
263
363
  * gutter to compensate for browser UA overrides on contenteditable.
264
364
  */
265
365
  protected _syncGutterHeights(): void;
@@ -269,7 +369,10 @@ export declare class RCTextarea extends LitElement {
269
369
  * `_gutterLabels` is updated first; otherwise the cached labels are reused
270
370
  * (called by the `ResizeObserver` without a new render pass).
271
371
  */
272
- protected _syncGutter(labels?: (string | null)[]): void;
372
+ protected _syncGutter(labels?: (string | HTMLElement | null | {
373
+ number: string;
374
+ content: string | HTMLElement;
375
+ })[]): void;
273
376
  protected _syncLabel(): void;
274
377
  protected _syncTypography($textarea: HTMLTextAreaElement): void;
275
378
  protected _syncTextareaValue(fromUser?: boolean): void;
@@ -28,6 +28,8 @@ interface DomPosition {
28
28
  * Returns null if offset is out of range.
29
29
  */
30
30
  export declare function textOffsetToDom($root: Element, targetOffset: number): DomPosition | null;
31
+ /** Returns the current selection focus node when it belongs to `$root`. */
32
+ export declare function getSelectionFocusNode($root: Element): Node | null;
31
33
  /**
32
34
  * Captures the current browser selection as plain-text offsets relative to `$root`.
33
35
  *
@@ -61,13 +61,19 @@ export interface LineDecoration {
61
61
  /** Additional HTML attributes set directly on the line element. */
62
62
  attributes?: Record<string, string>;
63
63
  /**
64
- * Override the built-in gutter cell text for this line.
64
+ * Override the built-in gutter cell content for this line.
65
65
  *
66
66
  * - `string` — custom label (e.g. `"!"`, `"▶"`)
67
+ * - `HTMLElement` — plugin-owned marker or control; reuse its node between updates.
68
+ * Set `gutterAccessible` when it contains an interactive control.
67
69
  * - `null` — force empty cell (suppress built-in content)
68
70
  * - `undefined` — use the built-in mode default (`line-numbers`, `gutter`, etc.)
69
71
  */
70
- gutterContent?: string | null;
72
+ gutterContent?: string | HTMLElement | null;
73
+ /** Keep the built-in line number before custom gutter content when `line-numbers` is enabled. */
74
+ gutterLineNumber?: boolean;
75
+ /** Expose this gutter cell to assistive technology when it contains a control. */
76
+ gutterAccessible?: boolean;
71
77
  }
72
78
  /**
73
79
  * A non-editable DOM element inserted at a character offset.
@@ -81,17 +87,36 @@ export interface WidgetDecoration {
81
87
  type: 'widget';
82
88
  /** Character offset. Widget is placed before (or after) the character at this position. */
83
89
  offset: number;
84
- /** Factory called each render cycle. Must return a new element each time. */
90
+ /** Create widget content when its line rebuilds. With a stable key, content may be reused. */
85
91
  create(): HTMLElement;
86
92
  /** Whether to place the widget before or after the character at `offset`. Default: 'before'. */
87
93
  side?: 'before' | 'after';
94
+ /** Preserve this widget's line DOM while its key and decorations remain unchanged.
95
+ * Change the key when its content changes. Scope keys to the owning plugin.
96
+ */
97
+ key?: string;
98
+ /** Place the widget inline, below its logical line, or in a reserved trailing lane. */
99
+ layout?: 'inline' | 'block' | 'trailing';
100
+ /** Expose interactive or meaningful widget content to assistive technology. Default: false. */
101
+ accessible?: boolean;
102
+ }
103
+ /** Hide complete source lines while retaining their text and model offsets. */
104
+ export interface FoldDecoration {
105
+ id: string;
106
+ type: 'fold';
107
+ /** First hidden line, one-based and inclusive. */
108
+ fromLine: number;
109
+ /** Last hidden line, one-based and inclusive. */
110
+ toLine: number;
111
+ /** Reveal this range before a user edit or caret move enters its hidden lines. */
112
+ reveal(): void;
88
113
  }
89
114
  /** Decorations are visual formatting applied to the field content without affecting
90
115
  * the underlying plain text value.
91
116
  */
92
- export type Decoration = MarkDecoration | LineDecoration | WidgetDecoration;
117
+ export type Decoration = MarkDecoration | LineDecoration | WidgetDecoration | FoldDecoration;
93
118
  /** Input shape for creating a decoration — id-less variant of each decoration type. */
94
- export type DecorationInput = Omit<MarkDecoration, 'id'> | Omit<LineDecoration, 'id'> | Omit<WidgetDecoration, 'id'>;
119
+ export type DecorationInput = Omit<MarkDecoration, 'id'> | Omit<LineDecoration, 'id'> | Omit<WidgetDecoration, 'id'> | Omit<FoldDecoration, 'id'>;
95
120
  /**
96
121
  * A semantic token produced by an external tokenizer (lezer, tree-sitter, shiki, etc.).
97
122
  *
@@ -138,6 +163,19 @@ export interface RCTextareaPluginAPI {
138
163
  readonly selectionStart: number;
139
164
  /** Normalized selection end offset (always ≥ `selectionStart`). Equals `selectionStart` when the cursor is collapsed. */
140
165
  readonly selectionEnd: number;
166
+ /**
167
+ * The editor's resolved base direction, from the textarea's `dir` or the
168
+ * host's. Anchor caret-relative popups with this, not the host's flow.
169
+ */
170
+ readonly contentDirection: 'ltr' | 'rtl';
171
+ /** The content language: the textarea's `lang`, or `''` when it sets none. */
172
+ readonly contentLang: string;
173
+ /**
174
+ * The resolved direction of a 1-based line. Under `line-direction="auto"`
175
+ * that is the direction of the line's first strong character; otherwise,
176
+ * and for lines without one, it is `contentDirection`.
177
+ */
178
+ lineDirection(line: number): 'ltr' | 'rtl';
141
179
  /**
142
180
  * Returns the bounding rect of the cursor (caret) in viewport coordinates.
143
181
  *
@@ -150,8 +188,13 @@ export interface RCTextareaPluginAPI {
150
188
  /**
151
189
  * Returns the word boundaries at the current cursor position.
152
190
  *
153
- * @returns the word and its `[from, to)` offsets, or `null` if the cursor is
154
- * not on a word character (`\w` — alphanumeric + underscore)
191
+ * Words come from `Intl.Segmenter` for `contentLang` (UAX #29, with
192
+ * dictionary segmentation for Japanese and Thai), so `crème`, `don't` and
193
+ * `snake_case` are each one word. The word starting at the cursor wins over
194
+ * the one ending there.
195
+ *
196
+ * @returns the word and its `[from, to)` offsets, or `null` if the cursor
197
+ * touches no letter or digit
155
198
  */
156
199
  getWordAtCursor(): {
157
200
  word: string;
@@ -341,8 +384,8 @@ export interface RCTextareaPlugin {
341
384
  /** Called when the plugin is replaced or the element disconnects. */
342
385
  destroy?(): void;
343
386
  /**
344
- * Display-layer value transform — called before `update`/`highlight` in
345
- * read-only mode.
387
+ * Display-layer value transform — called before `update`/`highlight`
388
+ * while the slotted textarea is `readonly`.
346
389
  *
347
390
  * Return a non-null string to substitute it as the rendered
348
391
  * text passed to all subsequent hooks and to the document builder.
package/package.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.0",
7
- "description": "Textarea wrapper with line decorations, gutter rendering, inline widgets, and plugin hooks.",
6
+ "version": "0.7.0",
7
+ "description": "Textarea wrapper with line decorations, gutter rendering, folding actions, and plugin hooks.",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
@@ -29,6 +29,12 @@
29
29
  "types": "./dist/types/packages/rc-textarea/src/define.d.ts",
30
30
  "default": "./dist/rc-textarea-define.js"
31
31
  }
32
+ },
33
+ "./folding": {
34
+ "import": {
35
+ "types": "./dist/types/packages/rc-textarea/src/folding.d.ts",
36
+ "default": "./dist/rc-textarea-folding.js"
37
+ }
32
38
  }
33
39
  },
34
40
  "types": "./dist/types/packages/rc-textarea/src/index.d.ts",
@@ -45,7 +51,7 @@
45
51
  "test:browser:firefox": "vitest --run --project=firefox"
46
52
  },
47
53
  "dependencies": {
48
- "@rcarls/rc-common": "0.5.0",
54
+ "@rcarls/rc-common": "0.7.0",
49
55
  "parchment": "^3.0.0"
50
56
  },
51
57
  "devDependencies": {