@oxy.so/bloom 4.22.2 → 4.23.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.
- package/docs/composer-panel.mdx +10 -0
- package/lib/commonjs/chat-composer/ChatComposer.js +10 -12
- package/lib/commonjs/chat-composer/ChatComposer.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPanelBase.js +9 -11
- package/lib/commonjs/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPillBase.js +24 -2
- package/lib/commonjs/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/commonjs/hooks/use-textarea-autosize.js +103 -0
- package/lib/commonjs/hooks/use-textarea-autosize.js.map +1 -0
- package/lib/module/chat-composer/ChatComposer.js +11 -13
- package/lib/module/chat-composer/ChatComposer.js.map +1 -1
- package/lib/module/composer-panel/ComposerPanelBase.js +10 -12
- package/lib/module/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/module/composer-panel/ComposerPillBase.js +24 -2
- package/lib/module/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/module/hooks/use-textarea-autosize.js +98 -0
- package/lib/module/hooks/use-textarea-autosize.js.map +1 -0
- package/lib/typescript/commonjs/chat-composer/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPillBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-textarea-autosize.d.ts +61 -0
- package/lib/typescript/commonjs/hooks/use-textarea-autosize.d.ts.map +1 -0
- package/lib/typescript/module/chat-composer/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerPillBase.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-textarea-autosize.d.ts +61 -0
- package/lib/typescript/module/hooks/use-textarea-autosize.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/chat-composer/ChatComposer.tsx +11 -13
- package/src/composer-panel/ComposerPanelBase.tsx +10 -12
- package/src/composer-panel/ComposerPillBase.tsx +28 -2
- package/src/hooks/use-textarea-autosize.ts +106 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { TextInput } from 'react-native';
|
|
3
|
+
/**
|
|
4
|
+
* The web half of a composer's autosize: how tall its draft is, measured from
|
|
5
|
+
* the `<textarea>` react-native-web renders. Native reads
|
|
6
|
+
* `onContentSizeChange` and never calls this.
|
|
7
|
+
*
|
|
8
|
+
* `scrollHeight` never falls below the height already applied, so it reports
|
|
9
|
+
* growth exactly but cannot report shrinking. The old measurement collapsed the
|
|
10
|
+
* live field to `0px` before EVERY read and restored it after — and a
|
|
11
|
+
* collapsed field is a different page: each keystroke paid for laying the
|
|
12
|
+
* whole document out three times (collapse, read, restore), 23–27 ms a key
|
|
13
|
+
* beside a 1,000-message conversation, against 6–8 ms without the collapse.
|
|
14
|
+
*
|
|
15
|
+
* So the field is collapsed only when the draft could have got SHORTER:
|
|
16
|
+
*
|
|
17
|
+
* - an edit that only INSERTED characters (typing, pasting, an IME
|
|
18
|
+
* committing) cannot remove a line, so `scrollHeight` is read as it
|
|
19
|
+
* stands: one layout, the same one the frame was going to do anyway.
|
|
20
|
+
* - a field already at its floor cannot shrink, so it is never collapsed.
|
|
21
|
+
* - anything else — a deletion, a replacement, a programmatic `value` such as
|
|
22
|
+
* the clear after send, a change of floor — collapses, reads and restores
|
|
23
|
+
* in the same task, so nothing is painted in between.
|
|
24
|
+
*
|
|
25
|
+
* The one thing insertion cannot promise is soft-wrap: inserting a character
|
|
26
|
+
* could in principle reflow a wrapped word onto fewer lines. The field then
|
|
27
|
+
* keeps one line too many until the next edit that deletes, which is the
|
|
28
|
+
* failure the old measurement would have fixed a keystroke earlier — not a
|
|
29
|
+
* clipped draft.
|
|
30
|
+
*/
|
|
31
|
+
export interface TextareaAutosizeOptions {
|
|
32
|
+
/** Measure at all — `IS_WEB`. Native measures through `onContentSizeChange`. */
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
/** The height the field has now, as last applied by the caller. */
|
|
35
|
+
height: number;
|
|
36
|
+
/** The smallest height the field is drawn at; a field there cannot shrink. */
|
|
37
|
+
minHeight: number;
|
|
38
|
+
/** Receives the draft's content height, in px, whenever it is measured. */
|
|
39
|
+
onMeasure: (contentHeight: number) => void;
|
|
40
|
+
}
|
|
41
|
+
/** The DOM surface this reads and writes — `HTMLTextAreaElement`, narrowed. */
|
|
42
|
+
export interface AutosizeNode {
|
|
43
|
+
style: {
|
|
44
|
+
height: string;
|
|
45
|
+
};
|
|
46
|
+
scrollHeight: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether `next` is `previous` with characters only INSERTED — at one place,
|
|
50
|
+
* nothing removed or replaced. A common prefix plus a common suffix that
|
|
51
|
+
* together cover all of `previous` means every old character survived.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isPureInsertion(previous: string, next: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The content height of `node`. With `collapse` it zeroes the height first so
|
|
56
|
+
* `scrollHeight` can report LESS than the field's current height, and puts the
|
|
57
|
+
* height back before returning.
|
|
58
|
+
*/
|
|
59
|
+
export declare function measureTextarea(node: AutosizeNode, collapse: boolean): number;
|
|
60
|
+
export declare function useTextareaAutosize(ref: RefObject<TextInput | null>, text: string, { enabled, height, minHeight, onMeasure }: TextareaAutosizeOptions): void;
|
|
61
|
+
//# sourceMappingURL=use-textarea-autosize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-textarea-autosize.d.ts","sourceRoot":"","sources":["../../../../src/hooks/use-textarea-autosize.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAO7E;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,EAChC,IAAI,EAAE,MAAM,EACZ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,uBAAuB,GACjE,IAAI,CAkBN"}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* app owns drafts per conversation and a composer that kept its own would lose
|
|
9
9
|
* them on every navigation.
|
|
10
10
|
*/
|
|
11
|
-
import React, { useCallback,
|
|
11
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import {
|
|
13
13
|
TextInput,
|
|
14
14
|
View,
|
|
@@ -31,6 +31,7 @@ import { RiForbidLine } from '../icons/remix/RiForbidLine';
|
|
|
31
31
|
import { RiMic2Line } from '../icons/remix/RiMic2Line';
|
|
32
32
|
import { RiSendPlaneLine } from '../icons/remix/RiSendPlaneLine';
|
|
33
33
|
import { useControllableState } from '../hooks/use-controllable-state';
|
|
34
|
+
import { useTextareaAutosize } from '../hooks/use-textarea-autosize';
|
|
34
35
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
35
36
|
import { useTheme } from '../theme/use-theme';
|
|
36
37
|
import { Text } from '../typography';
|
|
@@ -175,19 +176,16 @@ export function ChatComposer({
|
|
|
175
176
|
*
|
|
176
177
|
* They are not interchangeable: react-native-web reports `scrollHeight`,
|
|
177
178
|
* which never falls below the height already applied, so a field grown to six
|
|
178
|
-
* lines and then emptied would stay six lines tall.
|
|
179
|
-
*
|
|
179
|
+
* lines and then emptied would stay six lines tall. `useTextareaAutosize`
|
|
180
|
+
* collapses the node to measure it only when the draft may have got SHORTER —
|
|
181
|
+
* collapsing on every keystroke re-laid-out the whole page three times a key.
|
|
180
182
|
*/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const measured = node.scrollHeight;
|
|
188
|
-
node.style.height = previous;
|
|
189
|
-
if (measured > 0) setContentHeight(measured);
|
|
190
|
-
}, [text, minHeight]);
|
|
183
|
+
useTextareaAutosize(localRef, text, {
|
|
184
|
+
enabled: IS_WEB,
|
|
185
|
+
height,
|
|
186
|
+
minHeight,
|
|
187
|
+
onMeasure: setContentHeight,
|
|
188
|
+
});
|
|
191
189
|
|
|
192
190
|
const onContentSizeChange = useCallback(
|
|
193
191
|
(event: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect,
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
TextInput,
|
|
4
4
|
View,
|
|
@@ -17,6 +17,7 @@ import Animated, {
|
|
|
17
17
|
} from 'react-native-reanimated';
|
|
18
18
|
|
|
19
19
|
import { useControllableState } from '../hooks/use-controllable-state';
|
|
20
|
+
import { useTextareaAutosize } from '../hooks/use-textarea-autosize';
|
|
20
21
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
21
22
|
import { useTheme } from '../theme/use-theme';
|
|
22
23
|
import { TYPE_SCALE } from '../typography';
|
|
@@ -175,18 +176,15 @@ export function ComposerPanelBase({
|
|
|
175
176
|
);
|
|
176
177
|
|
|
177
178
|
// The prompt grows with its text, one 20px line at a time, up to 200 before it
|
|
178
|
-
// scrolls. Web measures the textarea
|
|
179
|
-
// native reads the content size.
|
|
179
|
+
// scrolls. Web measures the textarea, collapsing it only when the draft may
|
|
180
|
+
// have got shorter (`useTextareaAutosize`); native reads the content size.
|
|
180
181
|
const [height, setHeight] = useState(PROMPT_LINE);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
node.style.height = `${next}px`;
|
|
188
|
-
setHeight(next);
|
|
189
|
-
}, [text]);
|
|
182
|
+
useTextareaAutosize(fieldRef, text, {
|
|
183
|
+
enabled: IS_WEB,
|
|
184
|
+
height,
|
|
185
|
+
minHeight: PROMPT_LINE,
|
|
186
|
+
onMeasure: (content) => setHeight(Math.min(PROMPT_MAX_HEIGHT, Math.max(PROMPT_LINE, content))),
|
|
187
|
+
});
|
|
190
188
|
const onContentSizeChange = useCallback(
|
|
191
189
|
(event: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
|
|
192
190
|
if (IS_WEB) return;
|
|
@@ -16,6 +16,7 @@ import Animated, {
|
|
|
16
16
|
} from 'react-native-reanimated';
|
|
17
17
|
|
|
18
18
|
import { useControllableState } from '../hooks/use-controllable-state';
|
|
19
|
+
import { useTextareaAutosize } from '../hooks/use-textarea-autosize';
|
|
19
20
|
import { RiArrowDownSLine } from '../icons/remix/RiArrowDownSLine';
|
|
20
21
|
import { RadioIndicator } from '../radio-indicator';
|
|
21
22
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
@@ -459,6 +460,15 @@ export function ComposerPillBase({
|
|
|
459
460
|
const [contentHeight, setContentHeight] = useState(LINE_HEIGHT);
|
|
460
461
|
const [modelWidth, setModelWidth] = useState(0);
|
|
461
462
|
|
|
463
|
+
const fieldRef = useRef<TextInput | null>(null);
|
|
464
|
+
const setFieldRef = useCallback(
|
|
465
|
+
(node: TextInput | null) => {
|
|
466
|
+
fieldRef.current = node;
|
|
467
|
+
if (inputRef) (inputRef as React.MutableRefObject<TextInput | null>).current = node;
|
|
468
|
+
},
|
|
469
|
+
[inputRef],
|
|
470
|
+
);
|
|
471
|
+
|
|
462
472
|
// The add control shares the canonical inset and hover surfaces.
|
|
463
473
|
const addPalette: ComposerPalette = useMemo(() => {
|
|
464
474
|
if (glass) return { ...palette, add: 'transparent', addHover: 'transparent' };
|
|
@@ -547,6 +557,22 @@ export function ComposerPillBase({
|
|
|
547
557
|
const pillHeight = PILL_HEIGHT + (lines - 1) * LINE_HEIGHT;
|
|
548
558
|
const multiLine = lines > 1;
|
|
549
559
|
|
|
560
|
+
/*
|
|
561
|
+
* On web the field is measured, not reported. react-native-web's
|
|
562
|
+
* `onContentSizeChange` reads `scrollHeight` on input only, and that never
|
|
563
|
+
* falls below the height already applied: a pill grown to four lines stayed
|
|
564
|
+
* four lines tall after the draft was deleted, and after send — a
|
|
565
|
+
* programmatic clear fires no input at all. `useTextareaAutosize` measures on
|
|
566
|
+
* every change of the draft and collapses the field only when it may have
|
|
567
|
+
* got shorter.
|
|
568
|
+
*/
|
|
569
|
+
useTextareaAutosize(fieldRef, text, {
|
|
570
|
+
enabled: IS_WEB,
|
|
571
|
+
height: fieldHeight,
|
|
572
|
+
minHeight: LINE_HEIGHT,
|
|
573
|
+
onMeasure: setContentHeight,
|
|
574
|
+
});
|
|
575
|
+
|
|
550
576
|
const pillStyle: WebCssStyle = {
|
|
551
577
|
width: '100%',
|
|
552
578
|
height: pillHeight,
|
|
@@ -593,7 +619,7 @@ export function ComposerPillBase({
|
|
|
593
619
|
|
|
594
620
|
<View style={fieldStyle}>
|
|
595
621
|
<TextInput
|
|
596
|
-
ref={
|
|
622
|
+
ref={setFieldRef}
|
|
597
623
|
{...dataHook('bloomComposerInput')}
|
|
598
624
|
testID={testID ? `${testID}-input` : undefined}
|
|
599
625
|
accessibilityLabel={labels.message}
|
|
@@ -609,7 +635,7 @@ export function ComposerPillBase({
|
|
|
609
635
|
cursorColor={palette.accent500}
|
|
610
636
|
returnKeyType="send"
|
|
611
637
|
multiline
|
|
612
|
-
onContentSizeChange={(event) => setContentHeight(event.nativeEvent.contentSize.height)}
|
|
638
|
+
onContentSizeChange={IS_WEB ? undefined : (event) => setContentHeight(event.nativeEvent.contentSize.height)}
|
|
613
639
|
style={{
|
|
614
640
|
width: '100%',
|
|
615
641
|
height: fieldHeight,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, type RefObject } from 'react';
|
|
2
|
+
import type { TextInput } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The web half of a composer's autosize: how tall its draft is, measured from
|
|
6
|
+
* the `<textarea>` react-native-web renders. Native reads
|
|
7
|
+
* `onContentSizeChange` and never calls this.
|
|
8
|
+
*
|
|
9
|
+
* `scrollHeight` never falls below the height already applied, so it reports
|
|
10
|
+
* growth exactly but cannot report shrinking. The old measurement collapsed the
|
|
11
|
+
* live field to `0px` before EVERY read and restored it after — and a
|
|
12
|
+
* collapsed field is a different page: each keystroke paid for laying the
|
|
13
|
+
* whole document out three times (collapse, read, restore), 23–27 ms a key
|
|
14
|
+
* beside a 1,000-message conversation, against 6–8 ms without the collapse.
|
|
15
|
+
*
|
|
16
|
+
* So the field is collapsed only when the draft could have got SHORTER:
|
|
17
|
+
*
|
|
18
|
+
* - an edit that only INSERTED characters (typing, pasting, an IME
|
|
19
|
+
* committing) cannot remove a line, so `scrollHeight` is read as it
|
|
20
|
+
* stands: one layout, the same one the frame was going to do anyway.
|
|
21
|
+
* - a field already at its floor cannot shrink, so it is never collapsed.
|
|
22
|
+
* - anything else — a deletion, a replacement, a programmatic `value` such as
|
|
23
|
+
* the clear after send, a change of floor — collapses, reads and restores
|
|
24
|
+
* in the same task, so nothing is painted in between.
|
|
25
|
+
*
|
|
26
|
+
* The one thing insertion cannot promise is soft-wrap: inserting a character
|
|
27
|
+
* could in principle reflow a wrapped word onto fewer lines. The field then
|
|
28
|
+
* keeps one line too many until the next edit that deletes, which is the
|
|
29
|
+
* failure the old measurement would have fixed a keystroke earlier — not a
|
|
30
|
+
* clipped draft.
|
|
31
|
+
*/
|
|
32
|
+
export interface TextareaAutosizeOptions {
|
|
33
|
+
/** Measure at all — `IS_WEB`. Native measures through `onContentSizeChange`. */
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
/** The height the field has now, as last applied by the caller. */
|
|
36
|
+
height: number;
|
|
37
|
+
/** The smallest height the field is drawn at; a field there cannot shrink. */
|
|
38
|
+
minHeight: number;
|
|
39
|
+
/** Receives the draft's content height, in px, whenever it is measured. */
|
|
40
|
+
onMeasure: (contentHeight: number) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The DOM surface this reads and writes — `HTMLTextAreaElement`, narrowed. */
|
|
44
|
+
export interface AutosizeNode {
|
|
45
|
+
style: { height: string };
|
|
46
|
+
scrollHeight: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether `next` is `previous` with characters only INSERTED — at one place,
|
|
51
|
+
* nothing removed or replaced. A common prefix plus a common suffix that
|
|
52
|
+
* together cover all of `previous` means every old character survived.
|
|
53
|
+
*/
|
|
54
|
+
export function isPureInsertion(previous: string, next: string): boolean {
|
|
55
|
+
const length = previous.length;
|
|
56
|
+
if (next.length < length) return false;
|
|
57
|
+
let prefix = 0;
|
|
58
|
+
while (prefix < length && previous.charCodeAt(prefix) === next.charCodeAt(prefix)) prefix++;
|
|
59
|
+
if (prefix === length) return true;
|
|
60
|
+
let suffix = 0;
|
|
61
|
+
while (
|
|
62
|
+
suffix < length - prefix &&
|
|
63
|
+
previous.charCodeAt(length - 1 - suffix) === next.charCodeAt(next.length - 1 - suffix)
|
|
64
|
+
) {
|
|
65
|
+
suffix++;
|
|
66
|
+
}
|
|
67
|
+
return prefix + suffix >= length;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The content height of `node`. With `collapse` it zeroes the height first so
|
|
72
|
+
* `scrollHeight` can report LESS than the field's current height, and puts the
|
|
73
|
+
* height back before returning.
|
|
74
|
+
*/
|
|
75
|
+
export function measureTextarea(node: AutosizeNode, collapse: boolean): number {
|
|
76
|
+
if (!collapse) return node.scrollHeight;
|
|
77
|
+
const previous = node.style.height;
|
|
78
|
+
node.style.height = '0px';
|
|
79
|
+
const measured = node.scrollHeight;
|
|
80
|
+
node.style.height = previous;
|
|
81
|
+
return measured;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function useTextareaAutosize(
|
|
85
|
+
ref: RefObject<TextInput | null>,
|
|
86
|
+
text: string,
|
|
87
|
+
{ enabled, height, minHeight, onMeasure }: TextareaAutosizeOptions,
|
|
88
|
+
): void {
|
|
89
|
+
// What the field held at the last measurement, and the floor it was held to.
|
|
90
|
+
const last = useRef<{ text: string; minHeight: number } | null>(null);
|
|
91
|
+
const latest = useRef({ height, onMeasure });
|
|
92
|
+
latest.current = { height, onMeasure };
|
|
93
|
+
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
if (!enabled) return;
|
|
96
|
+
const node = ref.current as unknown as AutosizeNode | null;
|
|
97
|
+
if (!node?.style || typeof node.scrollHeight !== 'number') return;
|
|
98
|
+
const previous = last.current;
|
|
99
|
+
last.current = { text, minHeight };
|
|
100
|
+
const onlyGrew =
|
|
101
|
+
previous !== null && previous.minHeight === minHeight && isPureInsertion(previous.text, text);
|
|
102
|
+
const collapse = !onlyGrew && latest.current.height > minHeight;
|
|
103
|
+
const measured = measureTextarea(node, collapse);
|
|
104
|
+
if (measured > 0) latest.current.onMeasure(measured);
|
|
105
|
+
}, [enabled, ref, text, minHeight]);
|
|
106
|
+
}
|