@rcarls/rc-textarea 0.6.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.
@@ -0,0 +1,91 @@
1
+ class d {
2
+ constructor(e, o = {}) {
3
+ this.entries = /* @__PURE__ */ new Map(), this.gutterGroupElements = /* @__PURE__ */ new Map(), this.api = e, this.options = o;
4
+ }
5
+ updateIcon(e) {
6
+ const o = this.options.createTriggerIcon?.(e.expanded);
7
+ o ? (o.setAttribute("aria-hidden", "true"), o.part.add("fold-icon"), e.$button.replaceChildren(o)) : e.$button.textContent = e.expanded ? "⌃" : "⌄";
8
+ }
9
+ applyState(e) {
10
+ e.$button.setAttribute("aria-expanded", String(e.expanded)), e.target instanceof HTMLElement ? (e.target.id || (e.target.id = `rc-fold-${crypto.randomUUID()}`), e.$button.setAttribute("aria-controls", e.target.id), !e.expanded && e.target.contains(this.api.host.shadowRoot?.activeElement ?? null) && e.$button.focus(), e.target.hidden = !e.expanded) : e.$button.removeAttribute("aria-controls"), this.updateIcon(e);
11
+ }
12
+ /** Return trigger and collapsed-range decorations; callers provide any panel widget. */
13
+ getDecorations(e) {
14
+ const o = new Set(e.map((t) => t.key));
15
+ for (const [t, i] of this.entries)
16
+ o.has(t) || (this.release(i), this.entries.delete(t));
17
+ const s = [], u = /* @__PURE__ */ new Map();
18
+ for (const t of e) {
19
+ if (t.gutterLine === void 0 && t.offset === void 0)
20
+ throw new TypeError("A folding action requires a gutterLine or offset.");
21
+ if (!(t.target instanceof HTMLElement) && (t.gutterLine === void 0 || t.target.fromLine < 2 || t.target.fromLine <= t.gutterLine || t.target.toLine < t.target.fromLine))
22
+ throw new RangeError("A source fold requires a gutter anchor before its hidden lines.");
23
+ let i = this.entries.get(t.key);
24
+ if (!i) {
25
+ const r = document.createElement("button");
26
+ r.type = "button", r.className = "fold-action-trigger", r.part.add("fold-trigger");
27
+ const a = {
28
+ $button: r,
29
+ target: t.target,
30
+ expanded: t.initiallyExpanded ?? !0
31
+ };
32
+ this.entries.set(t.key, a), r.addEventListener("click", () => {
33
+ a.expanded = !a.expanded, this.applyState(a), this.api.scheduleUpdate();
34
+ }), i = a;
35
+ }
36
+ const n = i;
37
+ if (n.target = t.target, n.$button.setAttribute("aria-label", t.label), this.applyState(n), t.gutterLine !== void 0) {
38
+ n.$gutterContent ??= t.createGutterContent?.(n.$button) ?? n.$button, t.updateGutterContent?.(n.$gutterContent);
39
+ const r = u.get(t.gutterLine) ?? [];
40
+ r.push(n.$gutterContent), u.set(t.gutterLine, r);
41
+ } else t.offset !== void 0 && s.push({
42
+ type: "widget",
43
+ offset: t.offset,
44
+ side: "after",
45
+ key: `fold-trigger:${t.key}`,
46
+ layout: "trailing",
47
+ accessible: !0,
48
+ create: () => n.$button
49
+ });
50
+ !(t.target instanceof HTMLElement) && !n.expanded && t.target.toLine >= t.target.fromLine && s.push({
51
+ type: "fold",
52
+ fromLine: t.target.fromLine,
53
+ toLine: t.target.toLine,
54
+ reveal: () => {
55
+ n.expanded = !0, this.applyState(n), this.api.scheduleUpdate();
56
+ }
57
+ });
58
+ }
59
+ for (const t of this.gutterGroupElements.keys())
60
+ u.has(t) || this.gutterGroupElements.delete(t);
61
+ for (const [t, i] of u) {
62
+ let n = this.gutterGroupElements.get(t);
63
+ n || (n = document.createElement("span"), this.gutterGroupElements.set(t, n)), n.className = i.length > 1 ? "fold-action-group" : "fold-action-single", (i.length !== n.children.length || i.some((r, a) => n.children[a] !== r)) && n.replaceChildren(...i), s.push({
64
+ type: "line",
65
+ line: t,
66
+ gutterContent: n,
67
+ gutterAccessible: !0,
68
+ gutterLineNumber: !0
69
+ });
70
+ }
71
+ return s;
72
+ }
73
+ /** Get expansion state for an existing key. */
74
+ isExpanded(e) {
75
+ return this.entries.get(e)?.expanded;
76
+ }
77
+ /** Discard expansion state and return focus before removing a focused control. */
78
+ clear() {
79
+ for (const e of this.entries.values())
80
+ this.release(e);
81
+ this.entries.clear(), this.gutterGroupElements.clear();
82
+ }
83
+ release(e) {
84
+ const o = this.api.host.shadowRoot?.activeElement;
85
+ (o === e.$button || e.target instanceof HTMLElement && e.target.contains(o ?? null)) && this.api.host.shadowRoot?.querySelector('[part="editor"]')?.focus();
86
+ }
87
+ }
88
+ export {
89
+ d as F
90
+ };
91
+ //# sourceMappingURL=folding-action-controller-sxM3IomW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folding-action-controller-sxM3IomW.js","sources":["../src/folding-action-controller.ts"],"sourcesContent":["import type { DecorationInput, RCTextareaPluginAPI } from './types.js';\n\nexport interface SourceFoldRange {\n /** First source line hidden by the fold (one-based, inclusive). */\n fromLine: number;\n /** Last source line hidden by the fold (one-based, inclusive). */\n toLine: number;\n}\n\nexport interface FoldingAction {\n /** Stable identity for the expansion state across plugin updates. */\n key: string;\n /** Name the region and its context; aria-expanded supplies its current state. */\n label: string;\n /** Source lines or a caller-owned panel. The caller renders panel widgets separately. */\n target: SourceFoldRange | HTMLElement;\n /** One-based gutter line. Omit to place the button in the trailing lane. */\n gutterLine?: number;\n /** Source offset for a trailing-lane button; required without gutterLine. */\n offset?: number;\n /** Initial state for a newly seen key. Defaults to true. */\n initiallyExpanded?: boolean;\n /** Add decorative content beside the gutter button. */\n createGutterContent?: ($button: HTMLButtonElement) => HTMLElement;\n /** Refresh the cached gutter content without replacing its focused button. */\n updateGutterContent?: ($content: HTMLElement) => void;\n}\n\nexport interface FoldingActionControllerOptions {\n /** Render a decorative icon for the current expansion state. */\n createTriggerIcon?: (expanded: boolean) => Element | null;\n}\n\ninterface Entry {\n $button: HTMLButtonElement;\n $gutterContent?: HTMLElement;\n target: FoldingAction['target'];\n expanded: boolean;\n}\n\n/** Reconcile independent, accessible folding actions without owning their content. */\nexport class FoldingActionController {\n private readonly entries = new Map<string, Entry>();\n\n private readonly gutterGroupElements = new Map<number, HTMLElement>();\n\n private readonly api: RCTextareaPluginAPI;\n\n private readonly options: FoldingActionControllerOptions;\n\n constructor(api: RCTextareaPluginAPI, options: FoldingActionControllerOptions = {}) {\n this.api = api;\n this.options = options;\n }\n\n private updateIcon(entry: Entry): void {\n const $icon = this.options.createTriggerIcon?.(entry.expanded);\n\n if ($icon) {\n $icon.setAttribute('aria-hidden', 'true');\n $icon.part.add('fold-icon');\n entry.$button.replaceChildren($icon);\n } else {\n entry.$button.textContent = entry.expanded ? '⌃' : '⌄';\n }\n }\n\n private applyState(entry: Entry): void {\n entry.$button.setAttribute('aria-expanded', String(entry.expanded));\n\n if (entry.target instanceof HTMLElement) {\n if (!entry.target.id) {\n entry.target.id = `rc-fold-${crypto.randomUUID()}`;\n }\n\n entry.$button.setAttribute('aria-controls', entry.target.id);\n\n if (\n !entry.expanded &&\n entry.target.contains(this.api.host.shadowRoot?.activeElement ?? null)\n ) {\n entry.$button.focus();\n }\n\n entry.target.hidden = !entry.expanded;\n } else {\n entry.$button.removeAttribute('aria-controls');\n }\n\n this.updateIcon(entry);\n }\n\n /** Return trigger and collapsed-range decorations; callers provide any panel widget. */\n getDecorations(actions: readonly FoldingAction[]): DecorationInput[] {\n const keys = new Set(actions.map((action) => action.key));\n\n for (const [key, entry] of this.entries) {\n if (!keys.has(key)) {\n this.release(entry);\n this.entries.delete(key);\n }\n }\n\n const result: DecorationInput[] = [];\n const gutterGroups = new Map<number, HTMLElement[]>();\n\n for (const action of actions) {\n if (action.gutterLine === undefined && action.offset === undefined) {\n throw new TypeError('A folding action requires a gutterLine or offset.');\n }\n\n if (\n !(action.target instanceof HTMLElement) &&\n (action.gutterLine === undefined ||\n action.target.fromLine < 2 ||\n action.target.fromLine <= action.gutterLine ||\n action.target.toLine < action.target.fromLine)\n ) {\n throw new RangeError('A source fold requires a gutter anchor before its hidden lines.');\n }\n\n let found = this.entries.get(action.key);\n\n if (!found) {\n const $button = document.createElement('button');\n\n $button.type = 'button';\n $button.className = 'fold-action-trigger';\n $button.part.add('fold-trigger');\n\n const created: Entry = {\n $button,\n target: action.target,\n expanded: action.initiallyExpanded ?? true,\n };\n\n this.entries.set(action.key, created);\n\n $button.addEventListener('click', () => {\n created.expanded = !created.expanded;\n this.applyState(created);\n this.api.scheduleUpdate();\n });\n\n found = created;\n }\n\n // A const keeps the entry narrowed inside the callbacks below.\n const entry = found;\n\n entry.target = action.target;\n entry.$button.setAttribute('aria-label', action.label);\n\n this.applyState(entry);\n\n if (action.gutterLine !== undefined) {\n entry.$gutterContent ??= action.createGutterContent?.(entry.$button) ?? entry.$button;\n action.updateGutterContent?.(entry.$gutterContent);\n\n const group = gutterGroups.get(action.gutterLine) ?? [];\n\n group.push(entry.$gutterContent);\n gutterGroups.set(action.gutterLine, group);\n } else if (action.offset !== undefined) {\n result.push({\n type: 'widget',\n offset: action.offset,\n side: 'after',\n key: `fold-trigger:${action.key}`,\n layout: 'trailing',\n accessible: true,\n create: () => entry.$button,\n });\n }\n\n if (\n !(action.target instanceof HTMLElement) &&\n !entry.expanded &&\n action.target.toLine >= action.target.fromLine\n ) {\n result.push({\n type: 'fold',\n fromLine: action.target.fromLine,\n toLine: action.target.toLine,\n reveal: () => {\n entry.expanded = true;\n this.applyState(entry);\n this.api.scheduleUpdate();\n },\n });\n }\n }\n\n for (const line of this.gutterGroupElements.keys()) {\n if (!gutterGroups.has(line)) {\n this.gutterGroupElements.delete(line);\n }\n }\n\n for (const [line, contents] of gutterGroups) {\n let $group = this.gutterGroupElements.get(line);\n\n if (!$group) {\n $group = document.createElement('span');\n this.gutterGroupElements.set(line, $group);\n }\n\n $group.className = contents.length > 1 ? 'fold-action-group' : 'fold-action-single';\n\n if (\n contents.length !== $group.children.length ||\n contents.some((content, index) => $group.children[index] !== content)\n ) {\n $group.replaceChildren(...contents);\n }\n\n result.push({\n type: 'line',\n line,\n gutterContent: $group,\n gutterAccessible: true,\n gutterLineNumber: true,\n });\n }\n\n return result;\n }\n\n /** Get expansion state for an existing key. */\n isExpanded(key: string): boolean | undefined {\n return this.entries.get(key)?.expanded;\n }\n\n /** Discard expansion state and return focus before removing a focused control. */\n clear(): void {\n for (const entry of this.entries.values()) {\n this.release(entry);\n }\n\n this.entries.clear();\n this.gutterGroupElements.clear();\n }\n\n private release(entry: Entry): void {\n const $active = this.api.host.shadowRoot?.activeElement;\n\n if (\n $active === entry.$button ||\n (entry.target instanceof HTMLElement && entry.target.contains($active ?? null))\n ) {\n this.api.host.shadowRoot?.querySelector<HTMLElement>('[part=\"editor\"]')?.focus();\n }\n }\n}\n"],"names":["FoldingActionController","api","options","entry","$icon","actions","keys","action","key","result","gutterGroups","found","$button","created","group","line","contents","$group","content","index","$active"],"mappings":"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"}