@niivue/uikit 1.0.0-rc.2 → 1.0.0-rc.21

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 (41) hide show
  1. package/README.md +702 -8
  2. package/dist/button.d.ts +97 -0
  3. package/dist/buttonOverlay.d.ts +121 -0
  4. package/dist/colorControl.d.ts +130 -0
  5. package/dist/colorControlOverlay.d.ts +72 -0
  6. package/dist/controls.d.ts +151 -0
  7. package/dist/dialog.d.ts +138 -0
  8. package/dist/dialogOverlay.d.ts +115 -0
  9. package/dist/filePicker.d.ts +95 -0
  10. package/dist/filePickerOverlay.d.ts +75 -0
  11. package/dist/host.d.ts +87 -0
  12. package/dist/index.d.ts +61 -0
  13. package/dist/menu.d.ts +227 -0
  14. package/dist/menuOverlay.d.ts +149 -0
  15. package/dist/numberInput.d.ts +172 -0
  16. package/dist/numberInputOverlay.d.ts +131 -0
  17. package/dist/pointLabel.d.ts +68 -0
  18. package/dist/pointLabelOverlay.d.ts +39 -0
  19. package/dist/rect.d.ts +27 -0
  20. package/dist/rectOverlay.d.ts +13 -0
  21. package/dist/render/glRectRenderer.d.ts +13 -0
  22. package/dist/render/shaders.d.ts +3 -0
  23. package/dist/render/wgpuRectRenderer.d.ts +18 -0
  24. package/dist/scroll.d.ts +70 -0
  25. package/dist/segmented.d.ts +127 -0
  26. package/dist/segmentedOverlay.d.ts +87 -0
  27. package/dist/select.d.ts +66 -0
  28. package/dist/selectOverlay.d.ts +102 -0
  29. package/dist/slider.d.ts +152 -0
  30. package/dist/sliderOverlay.d.ts +92 -0
  31. package/dist/text/layout.d.ts +8 -0
  32. package/dist/textArea.d.ts +216 -0
  33. package/dist/textAreaOverlay.d.ts +145 -0
  34. package/dist/textEdit.d.ts +72 -0
  35. package/dist/textField.d.ts +33 -0
  36. package/dist/textInput.d.ts +123 -0
  37. package/dist/textInputOverlay.d.ts +139 -0
  38. package/dist/toggle.d.ts +115 -0
  39. package/dist/toggleOverlay.d.ts +80 -0
  40. package/dist/uikit.js +6269 -221
  41. package/package.json +3 -3
@@ -0,0 +1,152 @@
1
+ import { LineData } from './line';
2
+ import { RectData } from './rect';
3
+ import { UIKitFontMetrics } from './text/font';
4
+ import { RGBA } from './text/layout';
5
+ import { UIKitTextItem } from './textOverlay';
6
+ /** Every visual knob on a slider. All lengths are in canvas pixels. */
7
+ export interface SliderStyle {
8
+ trackHeight: number;
9
+ trackRadius: number;
10
+ /** Track color to the right of the thumb. */
11
+ trackFill: RGBA;
12
+ /** Track color from the left end to the thumb. */
13
+ trackActiveFill: RGBA;
14
+ /** Diameter of the (round) thumb. */
15
+ thumbSize: number;
16
+ thumbFill: RGBA;
17
+ thumbHoverFill: RGBA;
18
+ /** Thumb color while it is being dragged. */
19
+ thumbActiveFill: RGBA;
20
+ thumbBorder: RGBA;
21
+ thumbBorderWidth: number;
22
+ /** Tick marks under the track (drawn when the spec sets `tickStep`). */
23
+ tickColor: RGBA;
24
+ tickLength: number;
25
+ tickWidth: number;
26
+ textColor: RGBA;
27
+ disabledTextColor: RGBA;
28
+ /** Label and value em size. */
29
+ textSizePx: number;
30
+ /**
31
+ * Halo outline width around the label and value, so they read over any
32
+ * scene; 0 draws none. 1 to 2 reads best with the bundled font.
33
+ */
34
+ textOutlineWidth: number;
35
+ /** Halo color; null picks black or white for contrast with `textColor`. */
36
+ textOutlineColor: RGBA | null;
37
+ /** Space between the label row baseline and the thumb row. */
38
+ labelGap: number;
39
+ /** Focus ring color (drawn around the thumb while the slider has keyboard focus). */
40
+ focusRing: RGBA;
41
+ focusRingWidth: number;
42
+ }
43
+ export declare const DEFAULT_SLIDER_STYLE: SliderStyle;
44
+ export interface SliderSpec {
45
+ /** Stable key: the handle for updates, pointer tracking and the callbacks. */
46
+ id: string;
47
+ /** Optional label, drawn above the track at the left. */
48
+ label?: string;
49
+ /** Top-left corner of the whole control, in canvas pixels. */
50
+ x: number;
51
+ y: number;
52
+ /** Width of the whole control (the track spans it, inset by half the thumb). */
53
+ width: number;
54
+ min: number;
55
+ max: number;
56
+ /**
57
+ * Value granularity. Omit for a continuous slider; the keyboard then moves
58
+ * by one hundredth of the range.
59
+ */
60
+ step?: number;
61
+ /** Initial value (snapped to the range and step). */
62
+ value: number;
63
+ /** Draw the current value above the track at the right. Default false. */
64
+ showValue?: boolean;
65
+ /** Formatter for the displayed value. Default: the step's decimal places. */
66
+ format?: (value: number) => string;
67
+ /** Spacing of tick marks under the track, in value units. Omit for none. */
68
+ tickStep?: number;
69
+ /** A disabled slider draws dimmed and ignores input. Default true. */
70
+ enabled?: boolean;
71
+ /** Per-slider overrides of the overlay's default style. */
72
+ style?: Partial<SliderStyle>;
73
+ /** Fired for every value change during a drag or key press. */
74
+ onInput?: (value: number, id: string) => void;
75
+ /**
76
+ * Fired once when an interaction commits a changed value: on pointer release
77
+ * after a drag that moved it, and after each key press that moved it.
78
+ */
79
+ onChange?: (value: number, id: string) => void;
80
+ }
81
+ /** The control's box in canvas pixels plus the track and the thumb's rail. */
82
+ export interface SliderLayout {
83
+ x: number;
84
+ y: number;
85
+ width: number;
86
+ height: number;
87
+ /** The drawn track bar. */
88
+ track: {
89
+ x: number;
90
+ y: number;
91
+ width: number;
92
+ height: number;
93
+ };
94
+ /** Where the thumb centre may travel: from x0 (min) to x1 (max) along cy. */
95
+ rail: {
96
+ x0: number;
97
+ x1: number;
98
+ cy: number;
99
+ };
100
+ /** Baseline of the label row, or null when the slider has no label row. */
101
+ labelBaseline: number | null;
102
+ }
103
+ /** The transient state that changes how a slider looks. */
104
+ export interface SliderVisual {
105
+ value: number;
106
+ hover: boolean;
107
+ /** The thumb is being dragged. */
108
+ active: boolean;
109
+ focused: boolean;
110
+ enabled: boolean;
111
+ }
112
+ export declare function resolveSliderStyle(base: SliderStyle, override?: Partial<SliderStyle>): SliderStyle;
113
+ /** The number of decimal places needed to print multiples of `step` exactly (0 for no step). */
114
+ export declare function stepDecimals(step: number): number;
115
+ /** A `step` that can snap: finite and positive, else undefined (continuous). */
116
+ export declare function usableStep(step: number | undefined): number | undefined;
117
+ /** Round `value` to the nearest multiple of `step` from `origin`, printed exactly. */
118
+ export declare function snapToStep(value: number, origin: number, step: number): number;
119
+ /** Clamp `value` into [min, max] and, with a positive step, snap it to the step grid from min. */
120
+ export declare function snapValue(value: number, min: number, max: number, step?: number): number;
121
+ /** Position of `value` along the range as 0..1 (0 when the range is empty). */
122
+ export declare function valueToFraction(value: number, min: number, max: number): number;
123
+ export declare function fractionToValue(fraction: number, min: number, max: number): number;
124
+ /** The step a key press moves by: the spec's, or a hundredth of the range. */
125
+ export declare function effectiveStep(spec: Pick<SliderSpec, 'min' | 'max' | 'step'>): number;
126
+ /** Move `value` by one step (ten with `big`) in `direction` (+1 or -1), snapped. */
127
+ export declare function stepValue(value: number, spec: Pick<SliderSpec, 'min' | 'max' | 'step'>, direction: 1 | -1, big?: boolean): number;
128
+ /** The displayed text for a value: the spec's formatter, else the step's precision. */
129
+ export declare function formatSliderValue(spec: Pick<SliderSpec, 'step' | 'format'>, value: number): string;
130
+ /** The control box for a spec: an optional label row above the thumb row. */
131
+ export declare function layoutSlider(spec: SliderSpec, style: SliderStyle, metrics: UIKitFontMetrics): SliderLayout;
132
+ /** Scale every length in a spec and style by `k` (CSS pixels to canvas pixels). */
133
+ export declare function scaleSlider(spec: SliderSpec, style: SliderStyle, k: number): {
134
+ spec: SliderSpec;
135
+ style: SliderStyle;
136
+ };
137
+ /** The thumb centre x for a value. */
138
+ export declare function sliderThumbX(layout: SliderLayout, spec: Pick<SliderSpec, 'min' | 'max'>, value: number): number;
139
+ /** The (snapped) value for a pointer x along the rail. */
140
+ export declare function sliderValueAt(layout: SliderLayout, spec: Pick<SliderSpec, 'min' | 'max' | 'step'>, px: number): number;
141
+ /** True when canvas point (px, py) lies inside the control box. */
142
+ export declare function sliderContains(layout: SliderLayout, px: number, py: number): boolean;
143
+ /**
144
+ * The draw data for one slider in one visual state: the track with its active
145
+ * part filled to the thumb, tick marks, the thumb (with a focus ring when
146
+ * focused), and the label row.
147
+ */
148
+ export declare function buildSlider(spec: SliderSpec, style: SliderStyle, layout: SliderLayout, visual: SliderVisual): {
149
+ rects: RectData[];
150
+ lines: LineData[];
151
+ text: UIKitTextItem[];
152
+ };
@@ -0,0 +1,92 @@
1
+ import { UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
2
+ import { UIKitInteractive, UIKitKeyEvent, UIKitRedrawSource } from './controls';
3
+ import { SliderLayout, SliderSpec, SliderStyle } from './slider';
4
+ import { UIKitFont } from './text/font';
5
+ export interface UIKitSliderOverlayOptions {
6
+ /** Called whenever the overlay needs another frame. A control layer fills it in. */
7
+ requestRedraw?: () => void;
8
+ /** Default style for every slider; a spec's `style` overrides per key. */
9
+ style?: Partial<SliderStyle>;
10
+ /**
11
+ * Interpret spec positions and style lengths as CSS pixels and scale them by
12
+ * the frame's device pixel ratio, instead of canvas pixels.
13
+ */
14
+ cssUnits?: boolean;
15
+ }
16
+ export declare class UIKitSliderOverlay implements UIKitOverlayRenderer, UIKitInteractive {
17
+ readonly hoverCursor = "pointer";
18
+ private readonly font;
19
+ private readonly rects;
20
+ private readonly lines;
21
+ private readonly labels;
22
+ private readonly entries;
23
+ private baseStyle;
24
+ private readonly cssUnits;
25
+ private requestRedraw;
26
+ private scale;
27
+ private geometryDirty;
28
+ private dragId;
29
+ /** The value when the drag started, to decide whether release fires onChange. */
30
+ private dragStartValue;
31
+ private hoverId;
32
+ private focusedId;
33
+ private layer;
34
+ constructor(font: UIKitFont, options?: UIKitSliderOverlayOptions);
35
+ /** Replace the default style for every slider (per-spec overrides still win). */
36
+ setDefaultStyle(style: Partial<SliderStyle>): void;
37
+ /**
38
+ * Add a slider, or replace the one with the same id. The spec's `value` is
39
+ * snapped to its range and step; a replacement takes the new spec's value.
40
+ */
41
+ addSlider(spec: SliderSpec): void;
42
+ /** Replace the whole slider set. */
43
+ setSliders(specs: readonly SliderSpec[]): void;
44
+ removeSlider(id: string): void;
45
+ /**
46
+ * Patch one slider's spec (label, position, range, step, enabled, ...). The
47
+ * current value is kept (re-snapped to the new range) unless the patch sets
48
+ * `value`.
49
+ */
50
+ updateSlider(id: string, patch: Partial<Omit<SliderSpec, 'id'>>): void;
51
+ setEnabled(id: string, enabled: boolean): void;
52
+ /** Set a slider's value from code (snapped). Fires neither callback. */
53
+ setValue(id: string, value: number): void;
54
+ getValue(id: string): number | undefined;
55
+ /** The ids of all sliders, in draw order. */
56
+ get ids(): string[];
57
+ /** A slider's box in spec units (canvas pixels, or CSS pixels with `cssUnits`). */
58
+ getLayout(id: string): SliderLayout | null;
59
+ /** The slider with keyboard focus, if any. */
60
+ get focusedSlider(): string | null;
61
+ /** Give a slider keyboard focus, or null to clear. Disabled sliders refuse. */
62
+ focus(id: string | null): void;
63
+ /** True while a thumb is being dragged. */
64
+ get isDragging(): boolean;
65
+ hitTest(x: number, y: number): boolean;
66
+ /** Jumps the thumb to the pointer and starts a drag. */
67
+ pointerDown(x: number, y: number): boolean;
68
+ pointerMove(x: number, y: number): boolean;
69
+ /** Ends the drag; fires `onChange` if the value moved since the press. */
70
+ pointerUp(x: number, y: number): boolean;
71
+ /** Ends the drag where it is; fires `onChange` if the value moved. */
72
+ pointerCancel(): void;
73
+ /**
74
+ * Arrow keys step the focused slider (Shift: ten steps), PageUp/PageDown
75
+ * step by ten, Home/End jump to the ends.
76
+ */
77
+ keyDown(e: UIKitKeyEvent): boolean;
78
+ blur(): void;
79
+ bindLayer(layer: UIKitRedrawSource): void;
80
+ drawOverlay(frame: UIKitOverlayFrame): void;
81
+ /** Release GPU resources on both backends. */
82
+ destroy(): void;
83
+ private invalidate;
84
+ private endDrag;
85
+ /** Set an entry's value from a pointer x, firing onInput when it changes. */
86
+ private setFromPointer;
87
+ private scaled;
88
+ private layoutOf;
89
+ /** The topmost (last added) enabled slider under a canvas point. */
90
+ private hitEntry;
91
+ private rebuild;
92
+ }
@@ -34,6 +34,14 @@ export interface TextLayoutOptions {
34
34
  export declare function autoOutlineColor(fill: RGBA): RGBA;
35
35
  /** Total advance width of `str` in pixels at `sizePx`. */
36
36
  export declare function measureWidth(metrics: UIKitFontMetrics, str: string, sizePx: number): number;
37
+ /**
38
+ * Cap height of the font in em units: how far the top of a capital letter sits
39
+ * above the baseline. Used to centre a label on its button vertically by the
40
+ * capitals rather than by each string's own ink, so a row of buttons keeps one
41
+ * baseline whether or not a label has descenders. Falls back to a typical 0.7em
42
+ * when the atlas has no capital H.
43
+ */
44
+ export declare function capHeight(metrics: UIKitFontMetrics): number;
37
45
  /**
38
46
  * Normalize a line direction angle so text drawn along it is never upside down.
39
47
  * When the raw angle points leftward (cos < 0), the label would render mirrored;
@@ -0,0 +1,216 @@
1
+ import { UIKitKeyEvent } from './controls';
2
+ import { RectData } from './rect';
3
+ import { ScrollWindow, UIKitBox } from './scroll';
4
+ import { UIKitFontMetrics } from './text/font';
5
+ import { RGBA } from './text/layout';
6
+ import { TextEditState } from './textEdit';
7
+ import { UIKitTextItem } from './textOverlay';
8
+ /** Every visual knob on a text area. All lengths are in canvas pixels. */
9
+ export interface TextAreaStyle {
10
+ /** Field color at rest. */
11
+ fill: RGBA;
12
+ /** Field color while the pointer hovers it. */
13
+ hoverFill: RGBA;
14
+ /** Field color when disabled. */
15
+ disabledFill: RGBA;
16
+ border: RGBA;
17
+ /** Border color while the field has keyboard focus. */
18
+ focusBorder: RGBA;
19
+ borderWidth: number;
20
+ radius: number;
21
+ textColor: RGBA;
22
+ disabledTextColor: RGBA;
23
+ /** Color of the placeholder shown while the field is empty. */
24
+ placeholderColor: RGBA;
25
+ /** Label and field text em size. */
26
+ textSizePx: number;
27
+ /** Row height as a multiple of the em size. */
28
+ lineHeight: number;
29
+ /** Halo outline width around the label (it sits on the scene); 0 draws none. */
30
+ textOutlineWidth: number;
31
+ /** Halo color; null picks black or white for contrast with `textColor`. */
32
+ textOutlineColor: RGBA | null;
33
+ /** Space between the field's left border and the text. */
34
+ paddingX: number;
35
+ /** Space between the rows and the field's top and bottom edges. */
36
+ paddingY: number;
37
+ /** Space between a label and the field. */
38
+ labelGap: number;
39
+ /** Field width when the spec gives none. */
40
+ fieldWidth: number;
41
+ /** Visible rows when the spec gives none. */
42
+ rows: number;
43
+ caretColor: RGBA;
44
+ caretWidth: number;
45
+ /** Color behind selected text. */
46
+ selectionFill: RGBA;
47
+ /** Width of the scrollbar thumb shown when the rows overflow. */
48
+ scrollbarWidth: number;
49
+ /** Space between the text and the scrollbar, and between it and the border. */
50
+ scrollbarGap: number;
51
+ scrollbarColor: RGBA;
52
+ scrollbarTrackColor: RGBA;
53
+ }
54
+ export declare const DEFAULT_TEXT_AREA_STYLE: TextAreaStyle;
55
+ export interface TextAreaSpec {
56
+ /** Stable key: the handle for updates, pointer tracking and the callbacks. */
57
+ id: string;
58
+ /** Optional label, drawn above the field. */
59
+ label?: string;
60
+ /** Top-left corner of the whole control (label included), in canvas pixels. */
61
+ x: number;
62
+ y: number;
63
+ /** Field width. Default: the style's `fieldWidth`. */
64
+ width?: number;
65
+ /** Visible rows; longer text scrolls. Default: the style's `rows`. */
66
+ rows?: number;
67
+ /** Initial text. Default empty. */
68
+ value?: string;
69
+ /** Dimmed text shown while the field is empty. */
70
+ placeholder?: string;
71
+ /** Typing stops at this many characters. */
72
+ maxLength?: number;
73
+ /** Characters the field refuses from the keyboard (Enter is always a newline). */
74
+ accept?: (ch: string) => boolean;
75
+ /** A disabled area draws dimmed and ignores input. Default true. */
76
+ enabled?: boolean;
77
+ /** Per-area overrides of the overlay's default style. */
78
+ style?: Partial<TextAreaStyle>;
79
+ /** Fired on every keystroke that changes the text. */
80
+ onInput?: (text: string, id: string) => void;
81
+ /** Fired once when changed text is committed: on Meta or Ctrl plus Enter and on losing focus. */
82
+ onChange?: (text: string, id: string) => void;
83
+ /** Fired on Meta or Ctrl plus Enter, changed or not, after `onChange`. */
84
+ onSubmit?: (text: string, id: string) => void;
85
+ }
86
+ /** The control's boxes in canvas pixels. */
87
+ export interface TextAreaLayout {
88
+ /** The whole control: label and field. */
89
+ x: number;
90
+ y: number;
91
+ width: number;
92
+ height: number;
93
+ /** The bordered field. */
94
+ field: UIKitBox;
95
+ /** Where the rows of text, the caret and the selection draw. */
96
+ textArea: UIKitBox;
97
+ /** Baseline of the label (meaningful only with a label). */
98
+ labelBaseline: number;
99
+ /** Height of one row of text. */
100
+ rowHeight: number;
101
+ /** Rows the text area shows at once. */
102
+ rows: number;
103
+ }
104
+ /** The transient state that changes how a text area looks. */
105
+ export interface TextAreaVisual {
106
+ /** The text shown: the committed text, or the edit in progress. */
107
+ text: string;
108
+ /** The edit in progress while the area has keyboard focus, else null. */
109
+ edit: TextEditState | null;
110
+ /** Index of the first row the text area shows (the overlay keeps it between frames). */
111
+ firstRow: number;
112
+ /**
113
+ * Scroll `firstRow` just far enough to show the caret (the default). The
114
+ * overlay passes false after the user scrolled by wheel or thumb, so the
115
+ * drawn rows match the viewport its hit tests use.
116
+ */
117
+ revealCaret?: boolean;
118
+ hover: boolean;
119
+ enabled: boolean;
120
+ }
121
+ /** One visual row: the code units [start, end) of the text it shows. */
122
+ export interface TextRow {
123
+ start: number;
124
+ end: number;
125
+ /** True when the row ends its paragraph (at a newline or the end of the text). */
126
+ hard: boolean;
127
+ }
128
+ export declare function resolveTextAreaStyle(base: TextAreaStyle, override?: Partial<TextAreaStyle>): TextAreaStyle;
129
+ /** The control's boxes for a spec. */
130
+ export declare function layoutTextArea(spec: TextAreaSpec, style: TextAreaStyle, metrics: UIKitFontMetrics): TextAreaLayout;
131
+ /** Scale every length in a spec and style by `k` (CSS pixels to canvas pixels). */
132
+ export declare function scaleTextArea(spec: TextAreaSpec, style: TextAreaStyle, k: number): {
133
+ spec: TextAreaSpec;
134
+ style: TextAreaStyle;
135
+ };
136
+ /** True when canvas point (px, py) lies inside the field (the label is not interactive). */
137
+ export declare function textAreaContains(layout: TextAreaLayout, px: number, py: number): boolean;
138
+ /** The track and thumb for an overflowing area's row-based scrollbar. */
139
+ export interface TextAreaScrollbar {
140
+ track: UIKitBox;
141
+ thumb: UIKitBox;
142
+ /** Largest valid first-row index for this content and viewport. */
143
+ maxFirst: number;
144
+ }
145
+ /**
146
+ * Returns the scrollbar geometry when `rowCount` overflows `shown`, otherwise
147
+ * null. Keeping this calculation shared means the drawn thumb and its pointer
148
+ * hit target cannot drift apart.
149
+ */
150
+ export declare function textAreaScrollbar(layout: TextAreaLayout, style: TextAreaStyle, shown: ScrollWindow, rowCount: number): TextAreaScrollbar | null;
151
+ /** The width the rows have for glyphs, keeping room for the caret. */
152
+ export declare function textAreaWrapWidth(layout: TextAreaLayout, style: TextAreaStyle): number;
153
+ /**
154
+ * Break `text` into rows no wider than `maxWidth`: at every newline, else
155
+ * after the last space that fits (the space may overhang the edge), else
156
+ * before the first glyph that does not fit. Every row holds at least one
157
+ * code unit unless its paragraph is empty, and the newline that ends a
158
+ * paragraph belongs to no row (it sits at the hard row's `end`).
159
+ */
160
+ export declare function wrapRows(text: string, advances: readonly number[], maxWidth: number): TextRow[];
161
+ /**
162
+ * The row that shows the caret at `caret`: the row containing it, or a hard
163
+ * row ending exactly there. A caret at a soft break sits on the next row.
164
+ */
165
+ export declare function rowOfCaret(rows: readonly TextRow[], caret: number): number;
166
+ /** The x offset of `caret` from the left edge of its row. */
167
+ export declare function caretXInRow(advances: readonly number[], row: TextRow, caret: number): number;
168
+ /**
169
+ * The caret index nearest to `px` (measured from the row's left edge) on a
170
+ * row. Past the end of a soft row the caret lands before a trailing space,
171
+ * so it stays on that row.
172
+ */
173
+ export declare function caretInRow(text: string, advances: readonly number[], row: TextRow, px: number): number;
174
+ /**
175
+ * The run of rows the text area shows, starting at `firstRow` but scrolled
176
+ * the least distance that keeps `caret` in view.
177
+ */
178
+ export declare function textAreaWindow(layout: TextAreaLayout, rows: readonly TextRow[], firstRow: number, caret: number | null): ScrollWindow;
179
+ /** The rows a text falls into inside a layout. */
180
+ export declare function textAreaRows(layout: TextAreaLayout, style: TextAreaStyle, metrics: UIKitFontMetrics, text: string): TextRow[];
181
+ /**
182
+ * The caret index for a canvas point over text shown from `firstRow`: the
183
+ * row under `py` (clamped to the rows shown) and the nearest glyph edge on it.
184
+ */
185
+ export declare function textAreaCaretAt(layout: TextAreaLayout, style: TextAreaStyle, metrics: UIKitFontMetrics, text: string, firstRow: number, px: number, py: number): number;
186
+ /**
187
+ * Move the caret `delta` rows, keeping `goalX` (the x the caret had before a
188
+ * run of vertical moves, or its current x when null) as far as the target
189
+ * row allows. Past the first or last row the caret goes to the text's ends.
190
+ */
191
+ export declare function moveCaretRows(s: TextEditState, rows: readonly TextRow[], advances: readonly number[], delta: number, goalX: number | null, extend: boolean): {
192
+ state: TextEditState;
193
+ goalX: number;
194
+ };
195
+ /**
196
+ * The state after an editing key in a text area: Enter inserts a newline,
197
+ * ArrowUp and ArrowDown move by row (PageUp and PageDown by `pageRows`,
198
+ * Meta or Ctrl to the text's ends), Home and End move within the row (Meta
199
+ * or Ctrl to the text's ends), and every other key goes to the single-line
200
+ * model. `goalX` carries the x a run of vertical moves aims for; it comes
201
+ * back as null after any other key. Returns null for keys that are not edits.
202
+ */
203
+ export declare function textAreaKey(s: TextEditState, e: UIKitKeyEvent, rows: readonly TextRow[], advances: readonly number[], goalX: number | null, pageRows: number, accept?: (ch: string) => boolean): {
204
+ state: TextEditState;
205
+ goalX: number | null;
206
+ } | null;
207
+ /**
208
+ * The draw data for one text area in one visual state: the field, the
209
+ * placeholder or the selection, the visible rows of text, the caret, the
210
+ * scrollbar when the rows overflow, and the label.
211
+ */
212
+ export declare function buildTextArea(spec: TextAreaSpec, style: TextAreaStyle, layout: TextAreaLayout, metrics: UIKitFontMetrics, visual: TextAreaVisual): {
213
+ rects: RectData[];
214
+ text: UIKitTextItem[];
215
+ shown: ScrollWindow;
216
+ };
@@ -0,0 +1,145 @@
1
+ import { UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
2
+ import { UIKitInteractive, UIKitKeyEvent, UIKitRedrawSource } from './controls';
3
+ import { ClipboardBridge } from './host';
4
+ import { UIKitFont } from './text/font';
5
+ import { TextAreaLayout, TextAreaSpec, TextAreaStyle } from './textArea';
6
+ export interface UIKitTextAreaOverlayOptions {
7
+ /** Called whenever the overlay needs another frame. A control layer fills it in. */
8
+ requestRedraw?: () => void;
9
+ /** Default style for every area; a spec's `style` overrides per key. */
10
+ style?: Partial<TextAreaStyle>;
11
+ /**
12
+ * Interpret spec positions and style lengths as CSS pixels and scale them by
13
+ * the frame's device pixel ratio, instead of canvas pixels.
14
+ */
15
+ cssUnits?: boolean;
16
+ /** Two presses on a focused field within this many ms select all. Default 400. */
17
+ doubleClickMs?: number;
18
+ /** Clock for the double-click interval. Default `performance.now`. */
19
+ now?: () => number;
20
+ /** Where copy, cut and paste go. Default: the browser's async clipboard. */
21
+ clipboard?: ClipboardBridge;
22
+ }
23
+ export declare class UIKitTextAreaOverlay implements UIKitOverlayRenderer, UIKitInteractive {
24
+ private readonly font;
25
+ private readonly rects;
26
+ private readonly labels;
27
+ private readonly entries;
28
+ private baseStyle;
29
+ private readonly cssUnits;
30
+ private readonly doubleClickMs;
31
+ private readonly now;
32
+ private requestRedraw;
33
+ private layer;
34
+ private clipboard;
35
+ private scale;
36
+ private geometryDirty;
37
+ private hoverId;
38
+ private hoverScrollbarId;
39
+ private focusedId;
40
+ /** The held press, which drags the selection. */
41
+ private pressId;
42
+ /** The held scrollbar thumb and the pointer's offset from its top edge. */
43
+ private scrollDrag;
44
+ private lastDownId;
45
+ private lastDownAt;
46
+ constructor(font: UIKitFont, options?: UIKitTextAreaOverlayOptions);
47
+ /** CSS cursor while a field is hovered: resize over a scrollbar thumb. */
48
+ get hoverCursor(): string;
49
+ /** Replace the default style for every area (per-spec overrides still win). */
50
+ setDefaultStyle(style: Partial<TextAreaStyle>): void;
51
+ /**
52
+ * Add an area, or replace the one with the same id. A replacement takes
53
+ * the new spec's text and ends any edit in progress.
54
+ */
55
+ addTextArea(spec: TextAreaSpec): void;
56
+ /** Replace the whole area set. */
57
+ setTextAreas(specs: readonly TextAreaSpec[]): void;
58
+ removeTextArea(id: string): void;
59
+ /**
60
+ * Patch one area's spec (label, position, rows, placeholder, enabled, ...).
61
+ * The committed text is kept unless the patch sets `value`.
62
+ */
63
+ updateTextArea(id: string, patch: Partial<Omit<TextAreaSpec, 'id'>>): void;
64
+ setEnabled(id: string, enabled: boolean): void;
65
+ /** Set an area's text from code. Fires no callback; a focused field shows it selected. */
66
+ setValue(id: string, value: string): void;
67
+ /** The committed text. */
68
+ getValue(id: string): string | undefined;
69
+ /** The text the field shows: the edit in progress, or the committed text. */
70
+ getText(id: string): string | undefined;
71
+ /** The ids of all areas, in draw order. */
72
+ get ids(): string[];
73
+ /** An area's boxes in spec units (canvas pixels, or CSS pixels with `cssUnits`). */
74
+ getLayout(id: string): TextAreaLayout | null;
75
+ /** Index of the first row an area shows. */
76
+ getFirstRow(id: string): number | undefined;
77
+ /** The area with keyboard focus (being edited), if any. */
78
+ get focusedArea(): string | null;
79
+ /**
80
+ * Give an area keyboard focus with its whole text selected, or null to
81
+ * clear. Leaving an area commits its edit. Disabled areas refuse.
82
+ */
83
+ focus(id: string | null): void;
84
+ hitTest(x: number, y: number): boolean;
85
+ /**
86
+ * Focuses the field and places the caret at the pointer, starting a drag
87
+ * selection; a second press within the double-click interval selects all.
88
+ */
89
+ pointerDown(x: number, y: number): boolean;
90
+ pointerMove(x: number, y: number): boolean;
91
+ pointerUp(x: number, y: number): boolean;
92
+ pointerCancel(): void;
93
+ /** Scrolls the area under the pointer by whole rows. True when one is there. */
94
+ wheel(x: number, y: number, _deltaX: number, deltaY: number): boolean;
95
+ /** Meta or Ctrl plus Enter submits a dialog; a plain Enter is a new line. */
96
+ submitsForm(e: UIKitKeyEvent): boolean;
97
+ /**
98
+ * Editing keys change the text; Meta or Ctrl plus Enter commits and
99
+ * submits; Escape reverts.
100
+ */
101
+ keyDown(e: UIKitKeyEvent): boolean;
102
+ /**
103
+ * Paste text into the focused area from code, as a host that catches the
104
+ * DOM paste event itself would. Line breaks are kept as newlines; the
105
+ * spec's `accept` and `maxLength` apply.
106
+ */
107
+ paste(text: string): boolean;
108
+ /** Commits the edit in progress and drops focus. */
109
+ blur(): void;
110
+ bindLayer(layer: UIKitRedrawSource): void;
111
+ drawOverlay(frame: UIKitOverlayFrame): void;
112
+ /** Release GPU resources on both backends. */
113
+ destroy(): void;
114
+ /** Take a new edit state, redrawing and firing `onInput` when the text changed. */
115
+ private applyEdit;
116
+ /**
117
+ * Copy or cut the selection to the clipboard, or start a paste: the read
118
+ * is asynchronous, and its text lands only if the area is still being
119
+ * edited when it arrives.
120
+ */
121
+ private clipboardAction;
122
+ private invalidate;
123
+ private resetLayout;
124
+ /** The edit state for a freshly focused (or reverted) area: all selected. */
125
+ private freshEdit;
126
+ /**
127
+ * Commit the edit: changed text becomes the value and fires `onChange`.
128
+ * When the area stays focused the text is selected again.
129
+ */
130
+ private commit;
131
+ private shownText;
132
+ private rowsOf;
133
+ private caretAt;
134
+ /** Current thumb geometry, or null when the text fits without scrolling. */
135
+ private scrollbarOf;
136
+ /** Hit-test the visible thumb plus a gutter-sized pointer target. */
137
+ private thumbContains;
138
+ /** Map a dragged thumb top to the nearest whole-row viewport position. */
139
+ private setScrollFromThumb;
140
+ private scaled;
141
+ private layoutOf;
142
+ /** The topmost (last added) enabled area whose field is under a canvas point. */
143
+ private hitEntry;
144
+ private rebuild;
145
+ }