ropav 0.11.0 → 0.12.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/dist/components/autocomplete/autocomplete-clear-button.js +1 -1
- package/dist/components/autocomplete/autocomplete-filter.d.ts +1 -1
- package/dist/components/autocomplete/autocomplete-root.js +3 -0
- package/dist/components/checkbox/checkbox-root.js +4 -1
- package/dist/components/radio/radio-root.js +4 -1
- package/dist/components/scroll-shadow/scroll-shadow-root.js +2 -10
- package/dist/components/scroll-shadow/use-scroll-shadow.d.ts +10 -0
- package/dist/components/scroll-shadow/use-scroll-shadow.js +34 -2
- package/dist/components/select/index.d.ts +3 -2
- package/dist/components/select/index.js +2 -1
- package/dist/components/select/select-clear-button.d.ts +21 -0
- package/dist/components/select/select-clear-button.js +66 -0
- package/dist/components/select/select-root.d.ts +2 -1
- package/dist/components/select/select-root.js +15 -1
- package/dist/components/select/select.context.d.ts +11 -0
- package/dist/components/select/select.types.d.ts +3 -0
- package/dist/components/switch/switch-root.js +4 -1
- package/dist/components/table/table-row.js +3 -2
- package/dist/components/table/use-grid-keyboard.js +2 -3
- package/dist/components/table/use-table-collection.d.ts +0 -11
- package/dist/components/table/use-table-collection.js +1 -17
- package/dist/components/tabs/tabs-root.js +5 -1
- package/dist/components/tabs/tabs.types.d.ts +6 -0
- package/dist/components/textarea/textarea-root.js +20 -0
- package/dist/components/textarea/textarea.types.d.ts +21 -0
- package/dist/components/toast/index.d.ts +1 -1
- package/dist/components/toast/index.js +3 -3
- package/dist/components/toast/toast-default-content.js +26 -4
- package/dist/components/toast/toast-indicator.js +8 -2
- package/dist/components/toast/toast-provider.js +53 -5
- package/dist/components/toast/toast-queue.d.ts +60 -5
- package/dist/components/toast/toast-queue.js +85 -27
- package/dist/components/toast/toast-root.js +67 -14
- package/dist/components/toast/toast.constants.d.ts +17 -1
- package/dist/components/toast/toast.constants.js +18 -2
- package/dist/components/toast/toast.context.d.ts +6 -0
- package/dist/components/toast/toast.types.d.ts +19 -0
- package/dist/composables/use-list-keyboard.d.ts +1 -1
- package/dist/composables/use-list-keyboard.js +4 -5
- package/dist/composables/use-measured-height.d.ts +18 -10
- package/dist/composables/use-measured-height.js +53 -15
- package/dist/composables/use-overlay-position.js +7 -4
- package/dist/composables/use-select-state.d.ts +8 -0
- package/dist/composables/use-select-state.js +5 -0
- package/dist/composables/use-select.d.ts +7 -0
- package/dist/composables/use-select.js +6 -0
- package/dist/composables/use-textarea-autosize.d.ts +55 -0
- package/dist/composables/use-textarea-autosize.js +189 -0
- package/dist/composables/use-toast-region.d.ts +18 -4
- package/dist/composables/use-toast-region.js +128 -6
- package/dist/composables/use-toast.d.ts +1 -1
- package/dist/composables/use-toast.js +4 -3
- package/dist/composables/use-typeahead.js +3 -0
- package/dist/index.js +5 -4
- package/dist/ropav.min.css +1 -1
- package/dist/utils/focus.d.ts +13 -0
- package/dist/utils/focus.js +18 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -12,5 +12,21 @@ export declare const DEFAULT_MAX_VISIBLE_TOAST = 3;
|
|
|
12
12
|
export declare const DEFAULT_SCALE_FACTOR = 0.05;
|
|
13
13
|
/** Pixels wide, written out as `--toast-width` for the stylesheet to read. */
|
|
14
14
|
export declare const DEFAULT_TOAST_WIDTH = 460;
|
|
15
|
-
/** Milliseconds a toast lives for.
|
|
15
|
+
/** Milliseconds a toast lives for. */
|
|
16
16
|
export declare const DEFAULT_TOAST_TIMEOUT = 4000;
|
|
17
|
+
/**
|
|
18
|
+
* Milliseconds after which a closed toast is dropped whether or not it reported its exit.
|
|
19
|
+
*
|
|
20
|
+
* Not the length of the exit, which the toast waits out against its own animations. This is the
|
|
21
|
+
* ceiling for a toast that can never report one — a slot the caller filled with something other
|
|
22
|
+
* than a `ToastRoot` — so a list of them cannot grow without end.
|
|
23
|
+
*/
|
|
24
|
+
export declare const EXIT_FALLBACK = 2000;
|
|
25
|
+
/**
|
|
26
|
+
* The key combination that moves focus to the toast region.
|
|
27
|
+
*
|
|
28
|
+
* An entry naming one of the `KeyboardEvent` modifier booleans matches that modifier; anything
|
|
29
|
+
* else matches `event.code`, so what counts is the key in that position rather than the character
|
|
30
|
+
* the layout produces there. An empty list turns the shortcut off.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_HOTKEY: readonly string[];
|
|
@@ -13,7 +13,23 @@ var DEFAULT_MAX_VISIBLE_TOAST = 3;
|
|
|
13
13
|
var DEFAULT_SCALE_FACTOR = .05;
|
|
14
14
|
/** Pixels wide, written out as `--toast-width` for the stylesheet to read. */
|
|
15
15
|
var DEFAULT_TOAST_WIDTH = 460;
|
|
16
|
-
/** Milliseconds a toast lives for.
|
|
16
|
+
/** Milliseconds a toast lives for. */
|
|
17
17
|
var DEFAULT_TOAST_TIMEOUT = 4e3;
|
|
18
|
+
/**
|
|
19
|
+
* Milliseconds after which a closed toast is dropped whether or not it reported its exit.
|
|
20
|
+
*
|
|
21
|
+
* Not the length of the exit, which the toast waits out against its own animations. This is the
|
|
22
|
+
* ceiling for a toast that can never report one — a slot the caller filled with something other
|
|
23
|
+
* than a `ToastRoot` — so a list of them cannot grow without end.
|
|
24
|
+
*/
|
|
25
|
+
var EXIT_FALLBACK = 2e3;
|
|
26
|
+
/**
|
|
27
|
+
* The key combination that moves focus to the toast region.
|
|
28
|
+
*
|
|
29
|
+
* An entry naming one of the `KeyboardEvent` modifier booleans matches that modifier; anything
|
|
30
|
+
* else matches `event.code`, so what counts is the key in that position rather than the character
|
|
31
|
+
* the layout produces there. An empty list turns the shortcut off.
|
|
32
|
+
*/
|
|
33
|
+
var DEFAULT_HOTKEY = ["altKey", "KeyT"];
|
|
18
34
|
//#endregion
|
|
19
|
-
export { DEFAULT_GAP, DEFAULT_MAX_VISIBLE_TOAST, DEFAULT_SCALE_FACTOR, DEFAULT_TOAST_TIMEOUT, DEFAULT_TOAST_WIDTH };
|
|
35
|
+
export { DEFAULT_GAP, DEFAULT_HOTKEY, DEFAULT_MAX_VISIBLE_TOAST, DEFAULT_SCALE_FACTOR, DEFAULT_TOAST_TIMEOUT, DEFAULT_TOAST_WIDTH, EXIT_FALLBACK };
|
|
@@ -12,10 +12,16 @@ import type { ComputedRef } from "vue";
|
|
|
12
12
|
export interface ToastRegionContext {
|
|
13
13
|
/** Closes one toast by key. */
|
|
14
14
|
close: (key: string) => void;
|
|
15
|
+
/** Keys still mounted for their exit animation, which have left the queue already. */
|
|
16
|
+
exitingKeys: ComputedRef<ReadonlySet<string>>;
|
|
15
17
|
gap: ComputedRef<number>;
|
|
16
18
|
/** Every mounted toast's measured height, so a stacked toast can be clipped to the front one. */
|
|
17
19
|
heightsByKey: ComputedRef<Record<string, number>>;
|
|
20
|
+
/** Whether the stack is opened out, so every toast shows at its own height. */
|
|
21
|
+
isExpanded: ComputedRef<boolean>;
|
|
18
22
|
maxVisibleToasts: ComputedRef<number>;
|
|
23
|
+
/** Reported by a toast once its exit animation has finished, so it can be unmounted. */
|
|
24
|
+
onExitFinished: (key: string) => void;
|
|
19
25
|
onToastHeightChange: (key: string, height: number) => void;
|
|
20
26
|
onToastHeightRemove: (key: string) => void;
|
|
21
27
|
placement: ComputedRef<ToastVariants["placement"]>;
|
|
@@ -82,6 +82,19 @@ export interface ToastProviderProps {
|
|
|
82
82
|
class?: string;
|
|
83
83
|
/** Pixels between stacked toasts. @default 12 */
|
|
84
84
|
gap?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Key combination that moves focus to the region, as `KeyboardEvent` modifier names plus an
|
|
87
|
+
* `event.code`. Modifiers left out have to be up, so `Alt`+`T` does not answer `Ctrl`+`Alt`+`T`.
|
|
88
|
+
* Pass an empty list to turn it off.
|
|
89
|
+
*
|
|
90
|
+
* @default ["altKey", "KeyT"]
|
|
91
|
+
*/
|
|
92
|
+
hotkey?: readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* Holds the stack open regardless of pointer or focus. Deliberately does not pause the clocks:
|
|
95
|
+
* expansion asked for by the page is not someone reading the toasts.
|
|
96
|
+
*/
|
|
97
|
+
isExpanded?: boolean;
|
|
85
98
|
/**
|
|
86
99
|
* How many toasts are drawn at once. Visual only — the rest are faded out, not dropped.
|
|
87
100
|
*
|
|
@@ -118,6 +131,12 @@ export interface ToastContentProps {
|
|
|
118
131
|
}
|
|
119
132
|
export interface ToastIndicatorProps {
|
|
120
133
|
class?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Whether this indicator is replacing one the toast was already showing, which the stylesheet
|
|
136
|
+
* animates. The first indicator a toast renders is not a replacement and must not be marked as
|
|
137
|
+
* one, or every toast would arrive with its icon animating.
|
|
138
|
+
*/
|
|
139
|
+
isSwapped?: boolean;
|
|
121
140
|
/** Picks the default icon. Falls back to the toast's own variant. */
|
|
122
141
|
variant?: ToastVariants["variant"];
|
|
123
142
|
}
|
|
@@ -50,7 +50,7 @@ export interface UseListKeyboardOptions {
|
|
|
50
50
|
disallowSelectAll?: MaybeRefOrGetter<boolean | undefined>;
|
|
51
51
|
/** @default "clearSelection" */
|
|
52
52
|
escapeKeyBehavior?: MaybeRefOrGetter<"clearSelection" | "none" | undefined>;
|
|
53
|
-
/** Called when an item is activated
|
|
53
|
+
/** Called when an item is activated: Enter, or a press where there is no selection to make. */
|
|
54
54
|
onAction?: (key: CollectionKey) => void;
|
|
55
55
|
/**
|
|
56
56
|
* Whether focus over the collection is nominal rather than real.
|
|
@@ -278,14 +278,13 @@ var useListKeyboard = (options) => {
|
|
|
278
278
|
return;
|
|
279
279
|
}
|
|
280
280
|
case "Enter":
|
|
281
|
-
if (focused == null || toValue(options.disallowActivation)) return;
|
|
282
|
-
options.onAction?.(focused);
|
|
283
|
-
event.preventDefault();
|
|
284
|
-
return;
|
|
285
281
|
case " ":
|
|
286
282
|
if (focused == null || toValue(options.disallowActivation)) return;
|
|
287
283
|
if (selection.selectionMode.value === "none") options.onAction?.(focused);
|
|
288
|
-
else
|
|
284
|
+
else {
|
|
285
|
+
selection.select(focused, { isShiftPressed: event.shiftKey });
|
|
286
|
+
if (event.key === "Enter") options.onAction?.(focused);
|
|
287
|
+
}
|
|
289
288
|
event.preventDefault();
|
|
290
289
|
return;
|
|
291
290
|
default: return;
|
|
@@ -10,16 +10,24 @@ export interface UseMeasuredHeightReturn {
|
|
|
10
10
|
* the content *wants*: a toast that is not frontmost is clipped to the front one's height, so its
|
|
11
11
|
* own border box has already been overwritten by the very number this feeds.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* That overwriting is also why the height is unset before every reading. The value this produces
|
|
14
|
+
* is what decides the element's own `height`, so reading it back off a sized box would return the
|
|
15
|
+
* previous answer for ever — a toast whose content is replaced in place would keep the height of
|
|
16
|
+
* the content it no longer holds.
|
|
14
17
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Three narrowings against the usual shape, each measured rather than assumed:
|
|
19
|
+
*
|
|
20
|
+
* 1. It measures directly on attach and then lets the observers take over, rather than relying on
|
|
21
|
+
* a first notification alone. jsdom *has* a `ResizeObserver` constructor but it never notifies
|
|
22
|
+
* anything, so an observer-only reading is permanently absent there — and this value drives an
|
|
23
|
+
* inline custom property that decides layout, which is exactly the kind of thing a fast suite
|
|
24
|
+
* should be able to assert. In a real browser the two readings agree, and the repeat is
|
|
25
|
+
* dropped, so the only difference is that the first one lands a frame earlier.
|
|
26
|
+
* 2. Only the inline axis of a resize counts. The block axis is transitioned by the caller, and a
|
|
27
|
+
* reading forces layout twice to unset and restore the height — so reacting to it would
|
|
28
|
+
* re-measure on every frame of that animation and retarget the very transition it is watching.
|
|
29
|
+
* 3. Content is watched separately, because a box whose height is already forced does not resize
|
|
30
|
+
* when what is inside it changes. Attributes are left out of that watch: the caller writes its
|
|
31
|
+
* state onto this element, and none of it changes what the content wants.
|
|
24
32
|
*/
|
|
25
33
|
export declare const useMeasuredHeight: (elementRef: MaybeRefOrGetter<HTMLElement | null | undefined>) => UseMeasuredHeightReturn;
|
|
@@ -7,36 +7,74 @@ import { computed, onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
|
7
7
|
* the content *wants*: a toast that is not frontmost is clipped to the front one's height, so its
|
|
8
8
|
* own border box has already been overwritten by the very number this feeds.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* That overwriting is also why the height is unset before every reading. The value this produces
|
|
11
|
+
* is what decides the element's own `height`, so reading it back off a sized box would return the
|
|
12
|
+
* previous answer for ever — a toast whose content is replaced in place would keep the height of
|
|
13
|
+
* the content it no longer holds.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* Three narrowings against the usual shape, each measured rather than assumed:
|
|
16
|
+
*
|
|
17
|
+
* 1. It measures directly on attach and then lets the observers take over, rather than relying on
|
|
18
|
+
* a first notification alone. jsdom *has* a `ResizeObserver` constructor but it never notifies
|
|
19
|
+
* anything, so an observer-only reading is permanently absent there — and this value drives an
|
|
20
|
+
* inline custom property that decides layout, which is exactly the kind of thing a fast suite
|
|
21
|
+
* should be able to assert. In a real browser the two readings agree, and the repeat is
|
|
22
|
+
* dropped, so the only difference is that the first one lands a frame earlier.
|
|
23
|
+
* 2. Only the inline axis of a resize counts. The block axis is transitioned by the caller, and a
|
|
24
|
+
* reading forces layout twice to unset and restore the height — so reacting to it would
|
|
25
|
+
* re-measure on every frame of that animation and retarget the very transition it is watching.
|
|
26
|
+
* 3. Content is watched separately, because a box whose height is already forced does not resize
|
|
27
|
+
* when what is inside it changes. Attributes are left out of that watch: the caller writes its
|
|
28
|
+
* state onto this element, and none of it changes what the content wants.
|
|
21
29
|
*/
|
|
22
30
|
var useMeasuredHeight = (elementRef) => {
|
|
23
31
|
const height = shallowRef(void 0);
|
|
24
|
-
let
|
|
32
|
+
let resizeObserver;
|
|
33
|
+
let contentObserver;
|
|
34
|
+
let frame;
|
|
25
35
|
const detach = () => {
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
resizeObserver?.disconnect();
|
|
37
|
+
resizeObserver = void 0;
|
|
38
|
+
contentObserver?.disconnect();
|
|
39
|
+
contentObserver = void 0;
|
|
40
|
+
if (frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame);
|
|
41
|
+
frame = void 0;
|
|
28
42
|
};
|
|
29
43
|
watch(() => toValue(elementRef) ?? null, (element) => {
|
|
30
44
|
detach();
|
|
31
45
|
if (!element) return;
|
|
32
46
|
const measure = () => {
|
|
47
|
+
const forced = element.style.height;
|
|
48
|
+
element.style.height = "auto";
|
|
33
49
|
const next = element.scrollHeight;
|
|
50
|
+
element.style.height = forced;
|
|
34
51
|
if (height.value !== next) height.value = next;
|
|
35
52
|
};
|
|
53
|
+
const scheduleMeasure = () => {
|
|
54
|
+
if (frame !== void 0 || typeof requestAnimationFrame !== "function") return;
|
|
55
|
+
frame = requestAnimationFrame(() => {
|
|
56
|
+
frame = void 0;
|
|
57
|
+
measure();
|
|
58
|
+
});
|
|
59
|
+
};
|
|
36
60
|
measure();
|
|
61
|
+
if (typeof MutationObserver === "function") {
|
|
62
|
+
contentObserver = new MutationObserver(scheduleMeasure);
|
|
63
|
+
contentObserver.observe(element, {
|
|
64
|
+
characterData: true,
|
|
65
|
+
childList: true,
|
|
66
|
+
subtree: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
37
69
|
if (typeof ResizeObserver === "undefined") return;
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
let lastWidth = element.getBoundingClientRect().width;
|
|
71
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
72
|
+
const width = entries[0]?.contentRect.width;
|
|
73
|
+
if (width === void 0 || Math.abs(width - lastWidth) < .5) return;
|
|
74
|
+
lastWidth = width;
|
|
75
|
+
scheduleMeasure();
|
|
76
|
+
});
|
|
77
|
+
resizeObserver.observe(element);
|
|
40
78
|
}, {
|
|
41
79
|
flush: "post",
|
|
42
80
|
immediate: true
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { calculatePosition, getRect, translateRTL } from "../utils/position.js";
|
|
2
2
|
import { computed, onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
3
3
|
//#region src/composables/use-overlay-position.ts
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Overlays sit above the page. The value lives in the stylesheet so the modal-level surfaces,
|
|
6
|
+
* which can only declare theirs in CSS, resolve to the same level as this inline one.
|
|
7
|
+
*/
|
|
8
|
+
var OVERLAY_Z_INDEX = "var(--rp-z-index-overlay)";
|
|
6
9
|
/** Whether an element or one of its descendants holds focus. */
|
|
7
10
|
var isFocusWithin = (element) => {
|
|
8
11
|
const active = document.activeElement;
|
|
@@ -212,12 +215,12 @@ var useOverlayPosition = (options) => {
|
|
|
212
215
|
"max-height": "100vh",
|
|
213
216
|
position: "fixed",
|
|
214
217
|
top: "0px",
|
|
215
|
-
"z-index":
|
|
218
|
+
"z-index": OVERLAY_Z_INDEX
|
|
216
219
|
};
|
|
217
220
|
const style = {
|
|
218
221
|
"max-height": `${resolved.maxHeight}px`,
|
|
219
222
|
position: "absolute",
|
|
220
|
-
"z-index":
|
|
223
|
+
"z-index": OVERLAY_Z_INDEX
|
|
221
224
|
};
|
|
222
225
|
for (const [key, value] of Object.entries(resolved.position)) style[key] = `${value}px`;
|
|
223
226
|
style["--trigger-anchor-point"] = `${resolved.triggerAnchorPoint.x}px ${resolved.triggerAnchorPoint.y}px`;
|
|
@@ -61,6 +61,14 @@ export interface UseSelectStateReturn<T> extends MenuTriggerState, FormValidatio
|
|
|
61
61
|
/** The value a form reset goes back to. */
|
|
62
62
|
defaultValue: ComputedRef<SelectedValue>;
|
|
63
63
|
setValue: (value: SelectedValue) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Empties the selection.
|
|
66
|
+
*
|
|
67
|
+
* Not `selection.clearSelection()`: a single select sets `disallowEmptySelection`, which is
|
|
68
|
+
* what stops a press on the chosen option from un-choosing it — and which makes that call a
|
|
69
|
+
* no-op in the one mode most selects are in.
|
|
70
|
+
*/
|
|
71
|
+
clearValue: () => void;
|
|
64
72
|
/** The first chosen key, which is the whole value when single. */
|
|
65
73
|
selectedKey: ComputedRef<CollectionKey | null>;
|
|
66
74
|
selectedItems: ComputedRef<SelectedItem<T>[]>;
|
|
@@ -99,6 +99,10 @@ var useSelectState = (options) => {
|
|
|
99
99
|
return Array.isArray(current) && current.length === 0 ? null : current;
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
|
+
const clearValue = () => {
|
|
103
|
+
setValue(null);
|
|
104
|
+
validation.commitValidation();
|
|
105
|
+
};
|
|
102
106
|
const selection = useSelectionManager({
|
|
103
107
|
collection,
|
|
104
108
|
disabledBehavior: options.disabledBehavior,
|
|
@@ -132,6 +136,7 @@ var useSelectState = (options) => {
|
|
|
132
136
|
const canOpen = () => collection.size.value !== 0 || Boolean(toValue(options.allowsEmptyCollection));
|
|
133
137
|
return {
|
|
134
138
|
...validation,
|
|
139
|
+
clearValue,
|
|
135
140
|
close: trigger.close,
|
|
136
141
|
collection,
|
|
137
142
|
defaultValue,
|
|
@@ -10,6 +10,13 @@ export interface UseSelectOptions {
|
|
|
10
10
|
ariaLabelledby?: MaybeRefOrGetter<string | undefined>;
|
|
11
11
|
ariaDescribedby?: MaybeRefOrGetter<string | undefined>;
|
|
12
12
|
onFocusChange?: (isFocused: boolean) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Whether a clear button is composed into the select, which is what opens the trigger's
|
|
15
|
+
* Backspace/Delete shortcut. Read at event time, so registering one never costs a render.
|
|
16
|
+
*/
|
|
17
|
+
hasClearButton?: () => boolean;
|
|
18
|
+
/** Empties the selection. Called by the shortcut above. */
|
|
19
|
+
onClear?: () => void;
|
|
13
20
|
}
|
|
14
21
|
/** Attributes the trigger element renders, beside its own class and `data-slot`. */
|
|
15
22
|
export type SelectTriggerAttributes = Record<string, string | number | boolean | undefined>;
|
|
@@ -83,6 +83,12 @@ var useSelect = (options, state) => {
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
if ((event.key === "Backspace" || event.key === "Delete") && !state.isOpen.value && options.hasClearButton?.() && state.selectedItems.value.length > 0) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
state.clearValue();
|
|
89
|
+
options.onClear?.();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
86
92
|
typeahead.onKeydown(event);
|
|
87
93
|
if (event.defaultPrevented) return;
|
|
88
94
|
trigger.responder.handlers.value.onKeydown?.(event);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from "vue";
|
|
2
|
+
export interface UseTextareaAutosizeOptions {
|
|
3
|
+
element: MaybeRefOrGetter<HTMLTextAreaElement | null | undefined>;
|
|
4
|
+
/** When false, inline height and overflow are cleared and the native `rows` height is used. */
|
|
5
|
+
enabled: MaybeRefOrGetter<boolean | undefined>;
|
|
6
|
+
/** Floor, in rows. Ignored when `enabled` is false. */
|
|
7
|
+
minRows: MaybeRefOrGetter<number | undefined>;
|
|
8
|
+
/** Ceiling, in rows. Absent, the control grows without a cap. Ignored when `enabled` is false. */
|
|
9
|
+
maxRows: MaybeRefOrGetter<number | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Text held somewhere the element cannot be read from. Reading `element.value` is not
|
|
12
|
+
* reactive, so a caller that does not already remeasure when its own text moves passes
|
|
13
|
+
* the value it holds here.
|
|
14
|
+
*/
|
|
15
|
+
content?: MaybeRefOrGetter<unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface UseTextareaAutosizeSyncOptions {
|
|
18
|
+
/**
|
|
19
|
+
* From `input` only. A caret at the end then keeps the last line's padding in view.
|
|
20
|
+
* Layout remeasures omit this so a scroll the user made is not stolen.
|
|
21
|
+
*/
|
|
22
|
+
fromInput?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface UseTextareaAutosizeReturn {
|
|
25
|
+
/** Measure now. Needed from `input`, where the DOM already holds the next text. */
|
|
26
|
+
sync: (options?: UseTextareaAutosizeSyncOptions) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Keep a textarea as tall as its content, optionally between `minRows` and `maxRows`.
|
|
30
|
+
*
|
|
31
|
+
* The caller has to invoke {@link UseTextareaAutosizeReturn.sync} from `input`: reading
|
|
32
|
+
* `element.value` inside a getter is not a reactive dependency, and waiting for a post-flush
|
|
33
|
+
* watch would size against the previous stroke. `minRows`, `maxRows` and {@link
|
|
34
|
+
* UseTextareaAutosizeOptions.content} go through a post-flush watch instead.
|
|
35
|
+
*
|
|
36
|
+
* Observers attach only while autosize is on. Native `resize` writes inline `width` and
|
|
37
|
+
* `height` as the pointer drags; an observer that stayed attached with autosize off would
|
|
38
|
+
* `clear` that height on every width change and fight the handle.
|
|
39
|
+
*
|
|
40
|
+
* Writing `style.height` is itself a size change, so the observer has to discriminate width
|
|
41
|
+
* or it would loop. That makes the seed load-bearing: it is read from the same content box
|
|
42
|
+
* `contentRect` reports, because a seed off by the border makes the first delivery look like
|
|
43
|
+
* a width change, and skipping that first delivery instead loses a real one — `sync` after
|
|
44
|
+
* `observe` can make a scrollbar appear, and the callback reporting the narrower width is
|
|
45
|
+
* the only notice of it.
|
|
46
|
+
*
|
|
47
|
+
* An inline height also hides height-only metric changes from the observer — a webfont that
|
|
48
|
+
* lands after first paint, or `.rp-textarea`'s `@media (width >= 40rem)` type/padding switch
|
|
49
|
+
* on a fixed-width control. Those go through `document.fonts` and `window` `resize`. Used
|
|
50
|
+
* metrics that do not move the box (`--rp-leading`, Firefox text-only zoom) never notify at
|
|
51
|
+
* all, which is why every field settles with `overflow-y: auto` — a stale height then
|
|
52
|
+
* scrolls rather than clips. A leftover clip on a field no cap clamped is the observer
|
|
53
|
+
* backstop for same-width box changes such as padding, which do notify.
|
|
54
|
+
*/
|
|
55
|
+
export declare const useTextareaAutosize: (options: UseTextareaAutosizeOptions) => UseTextareaAutosizeReturn;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { onScopeDispose, toValue, watch } from "vue";
|
|
2
|
+
//#region src/composables/use-textarea-autosize.ts
|
|
3
|
+
var px = (value) => {
|
|
4
|
+
const parsed = Number.parseFloat(value);
|
|
5
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A used line-height, not the keyword.
|
|
9
|
+
*
|
|
10
|
+
* `normal` is a computed value the platform leaves unresolved, and a row count in `normal`s is
|
|
11
|
+
* not a length. Font-size times 1.2 is the usual stand-in, and is what a missing font-size falls
|
|
12
|
+
* back through as well.
|
|
13
|
+
*/
|
|
14
|
+
var lineHeightOf = (style) => {
|
|
15
|
+
const raw = style.lineHeight;
|
|
16
|
+
const parsed = Number.parseFloat(raw);
|
|
17
|
+
if (raw !== "normal" && Number.isFinite(parsed)) return parsed;
|
|
18
|
+
const fontSize = Number.parseFloat(style.fontSize);
|
|
19
|
+
return Number.isFinite(fontSize) ? fontSize * 1.2 : 16;
|
|
20
|
+
};
|
|
21
|
+
var positive = (value) => value !== void 0 && value > 0 ? value : void 0;
|
|
22
|
+
var clear = (element) => {
|
|
23
|
+
element.style.height = "";
|
|
24
|
+
element.style.overflowY = "";
|
|
25
|
+
element.style.scrollPaddingBottom = "";
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Content-box width, the same box ResizeObserver reports as `contentRect`.
|
|
29
|
+
*
|
|
30
|
+
* `clientWidth` is the padding box minus the scrollbar, so subtracting inline
|
|
31
|
+
* padding lands on the content box without mixing in the border the way
|
|
32
|
+
* `getBoundingClientRect` does.
|
|
33
|
+
*/
|
|
34
|
+
var contentWidthOf = (element) => {
|
|
35
|
+
const style = getComputedStyle(element);
|
|
36
|
+
return element.clientWidth - px(style.paddingLeft) - px(style.paddingRight);
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Size a textarea to its content, clamped to a row range.
|
|
40
|
+
*
|
|
41
|
+
* Measured on the live element rather than a hidden clone of it: a clone is a second node in
|
|
42
|
+
* the document to keep in step, and the live element already has the font, the padding and
|
|
43
|
+
* the wrapping width. Height is set to `auto` for the read so a previous inline height cannot
|
|
44
|
+
* pin `scrollHeight` to itself, then written back as a pixel height.
|
|
45
|
+
*
|
|
46
|
+
* `scrollHeight` includes padding and not the border. The reset is `border-box`, so the height
|
|
47
|
+
* that is written has to put the border back or the last line is clipped by that much.
|
|
48
|
+
*
|
|
49
|
+
* The bottom-pin is input-only. Typing at the end has to bring `padding-bottom` into view;
|
|
50
|
+
* a width, font or row-range remeasure must not yank a scroll the user just made.
|
|
51
|
+
*
|
|
52
|
+
* @returns Whether `maxRows` clamped the height, which is the one case where a field left
|
|
53
|
+
* scrolling its own content is doing what it was asked to.
|
|
54
|
+
*/
|
|
55
|
+
var measure = (element, minRows, maxRows, fromInput) => {
|
|
56
|
+
const style = getComputedStyle(element);
|
|
57
|
+
const paddingY = px(style.paddingTop) + px(style.paddingBottom);
|
|
58
|
+
const borderY = px(style.borderTopWidth) + px(style.borderBottomWidth);
|
|
59
|
+
const lineHeight = lineHeightOf(style);
|
|
60
|
+
const borderBox = style.boxSizing === "border-box";
|
|
61
|
+
const extras = borderBox ? paddingY + borderY : 0;
|
|
62
|
+
const min = positive(minRows);
|
|
63
|
+
const max = positive(maxRows);
|
|
64
|
+
const paddingBottom = px(style.paddingBottom);
|
|
65
|
+
const scrollTop = element.scrollTop;
|
|
66
|
+
const atEnd = element.selectionStart === element.value.length && element.selectionEnd === element.value.length;
|
|
67
|
+
if (min !== void 0) element.rows = min;
|
|
68
|
+
element.style.height = "auto";
|
|
69
|
+
element.style.overflowY = "hidden";
|
|
70
|
+
let height = element.scrollHeight;
|
|
71
|
+
if (borderBox) height += borderY;
|
|
72
|
+
else height -= paddingY;
|
|
73
|
+
if (min !== void 0) height = Math.max(height, min * lineHeight + extras);
|
|
74
|
+
let capped = false;
|
|
75
|
+
if (max !== void 0) {
|
|
76
|
+
const cap = max * lineHeight + extras;
|
|
77
|
+
if (height > cap) {
|
|
78
|
+
height = cap;
|
|
79
|
+
capped = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
element.style.height = `${height}px`;
|
|
83
|
+
element.style.overflowY = "auto";
|
|
84
|
+
element.style.scrollPaddingBottom = `${paddingBottom}px`;
|
|
85
|
+
if (fromInput && capped && atEnd) element.scrollTop = element.scrollHeight;
|
|
86
|
+
else element.scrollTop = scrollTop;
|
|
87
|
+
return capped;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Keep a textarea as tall as its content, optionally between `minRows` and `maxRows`.
|
|
91
|
+
*
|
|
92
|
+
* The caller has to invoke {@link UseTextareaAutosizeReturn.sync} from `input`: reading
|
|
93
|
+
* `element.value` inside a getter is not a reactive dependency, and waiting for a post-flush
|
|
94
|
+
* watch would size against the previous stroke. `minRows`, `maxRows` and {@link
|
|
95
|
+
* UseTextareaAutosizeOptions.content} go through a post-flush watch instead.
|
|
96
|
+
*
|
|
97
|
+
* Observers attach only while autosize is on. Native `resize` writes inline `width` and
|
|
98
|
+
* `height` as the pointer drags; an observer that stayed attached with autosize off would
|
|
99
|
+
* `clear` that height on every width change and fight the handle.
|
|
100
|
+
*
|
|
101
|
+
* Writing `style.height` is itself a size change, so the observer has to discriminate width
|
|
102
|
+
* or it would loop. That makes the seed load-bearing: it is read from the same content box
|
|
103
|
+
* `contentRect` reports, because a seed off by the border makes the first delivery look like
|
|
104
|
+
* a width change, and skipping that first delivery instead loses a real one — `sync` after
|
|
105
|
+
* `observe` can make a scrollbar appear, and the callback reporting the narrower width is
|
|
106
|
+
* the only notice of it.
|
|
107
|
+
*
|
|
108
|
+
* An inline height also hides height-only metric changes from the observer — a webfont that
|
|
109
|
+
* lands after first paint, or `.rp-textarea`'s `@media (width >= 40rem)` type/padding switch
|
|
110
|
+
* on a fixed-width control. Those go through `document.fonts` and `window` `resize`. Used
|
|
111
|
+
* metrics that do not move the box (`--rp-leading`, Firefox text-only zoom) never notify at
|
|
112
|
+
* all, which is why every field settles with `overflow-y: auto` — a stale height then
|
|
113
|
+
* scrolls rather than clips. A leftover clip on a field no cap clamped is the observer
|
|
114
|
+
* backstop for same-width box changes such as padding, which do notify.
|
|
115
|
+
*/
|
|
116
|
+
var useTextareaAutosize = (options) => {
|
|
117
|
+
let observer;
|
|
118
|
+
let lastWidth = NaN;
|
|
119
|
+
/** So turning autosize off does not wipe a height the native resize handle wrote. */
|
|
120
|
+
let applied = false;
|
|
121
|
+
/** Whether `maxRows` clamped the last measure, which is when a clip is meant to be there. */
|
|
122
|
+
let capped = false;
|
|
123
|
+
const sync = (syncOptions) => {
|
|
124
|
+
const element = toValue(options.element) ?? null;
|
|
125
|
+
const enabled = Boolean(toValue(options.enabled));
|
|
126
|
+
if (!element) return;
|
|
127
|
+
if (!enabled) {
|
|
128
|
+
if (applied) {
|
|
129
|
+
clear(element);
|
|
130
|
+
applied = false;
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
capped = measure(element, toValue(options.minRows), toValue(options.maxRows), Boolean(syncOptions?.fromInput));
|
|
135
|
+
applied = true;
|
|
136
|
+
};
|
|
137
|
+
const onMetrics = () => {
|
|
138
|
+
sync();
|
|
139
|
+
};
|
|
140
|
+
const metrics = (bind) => {
|
|
141
|
+
if (typeof document === "undefined") return;
|
|
142
|
+
window[bind]("resize", onMetrics);
|
|
143
|
+
document.fonts?.[bind]("loadingdone", onMetrics);
|
|
144
|
+
};
|
|
145
|
+
const detach = () => {
|
|
146
|
+
observer?.disconnect();
|
|
147
|
+
observer = void 0;
|
|
148
|
+
metrics("removeEventListener");
|
|
149
|
+
};
|
|
150
|
+
const observe = (element) => {
|
|
151
|
+
detach();
|
|
152
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
153
|
+
lastWidth = contentWidthOf(element);
|
|
154
|
+
observer = new ResizeObserver((entries) => {
|
|
155
|
+
const width = entries[0]?.contentRect.width;
|
|
156
|
+
if (width === void 0) return;
|
|
157
|
+
const widthChanged = width !== lastWidth;
|
|
158
|
+
lastWidth = width;
|
|
159
|
+
if (widthChanged) {
|
|
160
|
+
sync();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!capped && element.scrollHeight > element.clientHeight) sync();
|
|
164
|
+
});
|
|
165
|
+
observer.observe(element);
|
|
166
|
+
}
|
|
167
|
+
metrics("addEventListener");
|
|
168
|
+
};
|
|
169
|
+
watch([() => toValue(options.element) ?? null, () => Boolean(toValue(options.enabled))], ([element, enabled]) => {
|
|
170
|
+
detach();
|
|
171
|
+
if (!element) return;
|
|
172
|
+
if (enabled) observe(element);
|
|
173
|
+
sync();
|
|
174
|
+
}, {
|
|
175
|
+
flush: "post",
|
|
176
|
+
immediate: true
|
|
177
|
+
});
|
|
178
|
+
watch([
|
|
179
|
+
() => toValue(options.minRows),
|
|
180
|
+
() => toValue(options.maxRows),
|
|
181
|
+
() => toValue(options.content)
|
|
182
|
+
], () => {
|
|
183
|
+
sync();
|
|
184
|
+
}, { flush: "post" });
|
|
185
|
+
onScopeDispose(detach, true);
|
|
186
|
+
return { sync };
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
export { useTextareaAutosize };
|