ropav 0.11.1 → 0.12.1
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/AGENTS.md +150 -0
- package/README.md +5 -1
- package/dist/components/autocomplete/autocomplete-clear-button.js +1 -1
- package/dist/components/autocomplete/autocomplete-root.js +3 -0
- package/dist/components/checkbox/checkbox-root.js +6 -1
- package/dist/components/combo-box/combo-box-root.js +1 -0
- package/dist/components/date-picker/date-picker-root.js +1 -0
- package/dist/components/date-range-picker/date-range-picker-root.js +1 -0
- package/dist/components/form/form-root.js +7 -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/search-field/search-field-group.js +21 -1
- 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 +16 -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 +6 -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/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-checkbox-group-state.js +2 -0
- package/dist/composables/use-color-field-state.js +1 -0
- package/dist/composables/use-combo-box-state.d.ts +2 -0
- package/dist/composables/use-combo-box-state.js +3 -1
- package/dist/composables/use-date-field-state.js +1 -0
- package/dist/composables/use-date-picker-state.d.ts +2 -0
- package/dist/composables/use-date-picker-state.js +1 -0
- package/dist/composables/use-date-range-picker-state.d.ts +2 -0
- package/dist/composables/use-date-range-picker-state.js +2 -0
- package/dist/composables/use-description.js +1 -0
- package/dist/composables/use-form-validation-state.d.ts +48 -5
- package/dist/composables/use-form-validation-state.js +76 -7
- 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-number-field-state.js +1 -0
- package/dist/composables/use-overlay-position.js +7 -4
- package/dist/composables/use-radio-group-state.js +2 -0
- package/dist/composables/use-select-state.d.ts +10 -0
- package/dist/composables/use-select-state.js +7 -0
- package/dist/composables/use-select.d.ts +7 -0
- package/dist/composables/use-select.js +6 -0
- package/dist/composables/use-text-field.js +1 -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-toolbar.js +3 -1
- package/dist/composables/use-typeahead.js +3 -0
- package/dist/index.js +6 -5
- 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 +5 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./toast.constants.js";
|
|
1
|
+
import { DEFAULT_TOAST_TIMEOUT } from "./toast.constants.js";
|
|
2
2
|
import { computed, nextTick, onScopeDispose, shallowRef, toValue, watch } from "vue";
|
|
3
3
|
//#region src/components/toast/toast-queue.ts
|
|
4
4
|
/**
|
|
@@ -16,7 +16,9 @@ var Timer = class {
|
|
|
16
16
|
this.callback = callback;
|
|
17
17
|
this.remaining = delay;
|
|
18
18
|
}
|
|
19
|
+
/** Starts the clock again on `delay`, whether or not it was already running. */
|
|
19
20
|
reset(delay) {
|
|
21
|
+
this.pause();
|
|
20
22
|
this.remaining = delay;
|
|
21
23
|
this.resume();
|
|
22
24
|
}
|
|
@@ -27,7 +29,7 @@ var Timer = class {
|
|
|
27
29
|
this.remaining -= Date.now() - this.startTime;
|
|
28
30
|
}
|
|
29
31
|
resume() {
|
|
30
|
-
if (this.remaining <= 0) return;
|
|
32
|
+
if (this.timerId != null || this.remaining <= 0) return;
|
|
31
33
|
this.startTime = Date.now();
|
|
32
34
|
this.timerId = setTimeout(() => {
|
|
33
35
|
this.timerId = null;
|
|
@@ -40,7 +42,7 @@ var Timer = class {
|
|
|
40
42
|
* The order toasts are shown in, and the clocks that close them.
|
|
41
43
|
*
|
|
42
44
|
* Ported from `react-stately`'s `ToastQueue`, merged with the wrapper that normally sits around
|
|
43
|
-
* it.
|
|
45
|
+
* it. There those are two classes because the wrapper had a primitive to wrap; writing both
|
|
44
46
|
* here means keeping them apart would be two paths doing one job. What the merge keeps from the
|
|
45
47
|
* wrapper: the default timeout, the serialized view transition, and `maxVisibleToasts` as a hint
|
|
46
48
|
* the region reads rather than a truncation.
|
|
@@ -58,12 +60,8 @@ var ToastQueue = class {
|
|
|
58
60
|
visibleToasts = [];
|
|
59
61
|
constructor(options = {}) {
|
|
60
62
|
this.maxVisibleToasts = options.maxVisibleToasts;
|
|
61
|
-
|
|
62
|
-
this.resetTransitions = transitions.reset;
|
|
63
|
-
this.wrapUpdate = options.wrapUpdate ?? transitions.wrapUpdate;
|
|
63
|
+
this.wrapUpdate = options.wrapUpdate ?? ((fn) => fn());
|
|
64
64
|
}
|
|
65
|
-
/** Forgets any transitions still queued. For a test, since a chain outlives a mount. */
|
|
66
|
-
resetTransitions;
|
|
67
65
|
/** Adds a toast and returns its key. */
|
|
68
66
|
add(content, options = {}) {
|
|
69
67
|
const key = `_${Math.random().toString(36).slice(2)}`;
|
|
@@ -75,9 +73,40 @@ var ToastQueue = class {
|
|
|
75
73
|
timeout,
|
|
76
74
|
timer: timeout ? new Timer(() => this.close(key), timeout) : void 0
|
|
77
75
|
});
|
|
78
|
-
this.
|
|
76
|
+
this.notify("add");
|
|
79
77
|
return key;
|
|
80
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Replaces a toast's content where it stands, keeping its key and its place in the stack.
|
|
81
|
+
* Returns `false` for a key the queue no longer holds.
|
|
82
|
+
*
|
|
83
|
+
* Options the caller leaves out are kept rather than resolved: a toast asked to stay until
|
|
84
|
+
* something closes it must not pick up the default life because its message changed. That is
|
|
85
|
+
* why this reads the keys that are *present* instead of their values — an explicit `undefined`
|
|
86
|
+
* is a caller clearing a handler, and an absent key is a caller not mentioning one.
|
|
87
|
+
*
|
|
88
|
+
* The entry is replaced rather than mutated so the toast re-renders. The list is shallow, so a
|
|
89
|
+
* toast whose fields changed underneath it is the same object as far as anything watching is
|
|
90
|
+
* concerned; a new one keyed the same patches the node it already has.
|
|
91
|
+
*/
|
|
92
|
+
update(key, content, options = {}) {
|
|
93
|
+
const index = this.queue.findIndex((toast) => toast.key === key);
|
|
94
|
+
if (index < 0) return false;
|
|
95
|
+
const previous = this.queue[index];
|
|
96
|
+
const next = {
|
|
97
|
+
...previous,
|
|
98
|
+
content
|
|
99
|
+
};
|
|
100
|
+
if ("onClose" in options) next.onClose = options.onClose;
|
|
101
|
+
if ("timeout" in options) {
|
|
102
|
+
previous.timer?.pause();
|
|
103
|
+
next.timeout = options.timeout;
|
|
104
|
+
next.timer = options.timeout ? new Timer(() => this.close(key), options.timeout) : void 0;
|
|
105
|
+
}
|
|
106
|
+
this.queue[index] = next;
|
|
107
|
+
this.notify("add");
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
81
110
|
/** Closes one toast. Notifies even for a key that is no longer held. */
|
|
82
111
|
close(key) {
|
|
83
112
|
const index = this.queue.findIndex((toast) => toast.key === key);
|
|
@@ -85,12 +114,12 @@ var ToastQueue = class {
|
|
|
85
114
|
this.queue[index].onClose?.();
|
|
86
115
|
this.queue.splice(index, 1);
|
|
87
116
|
}
|
|
88
|
-
this.
|
|
117
|
+
this.notify("remove");
|
|
89
118
|
}
|
|
90
119
|
/** Drops every toast at once. Deliberately does not run their `onClose` — nothing closed them. */
|
|
91
120
|
clear() {
|
|
92
121
|
this.queue = [];
|
|
93
|
-
this.
|
|
122
|
+
this.notify("clear");
|
|
94
123
|
}
|
|
95
124
|
/** Stops the clocks of the toasts on screen. */
|
|
96
125
|
pauseAll() {
|
|
@@ -107,7 +136,7 @@ var ToastQueue = class {
|
|
|
107
136
|
this.subscriptions.delete(fn);
|
|
108
137
|
};
|
|
109
138
|
}
|
|
110
|
-
|
|
139
|
+
notify(action) {
|
|
111
140
|
this.visibleToasts = [...this.queue];
|
|
112
141
|
this.wrapUpdate(() => {
|
|
113
142
|
for (const fn of [...this.subscriptions]) fn();
|
|
@@ -115,7 +144,21 @@ var ToastQueue = class {
|
|
|
115
144
|
}
|
|
116
145
|
};
|
|
117
146
|
/**
|
|
118
|
-
* A queue's own chain of view transitions, so its updates animate one after another.
|
|
147
|
+
* A queue's own chain of view transitions, so its updates animate one after another. Hand the
|
|
148
|
+
* `wrapUpdate` it returns to a queue to animate that queue's toasts this way.
|
|
149
|
+
*
|
|
150
|
+
* Not what a queue does by default, and the reason is measured rather than assumed: a chain is
|
|
151
|
+
* serial, so under a burst each toast waits for every transition queued ahead of it — the fifth
|
|
152
|
+
* toast of five added 80ms apart took 1.5s to appear, against 0.37s when the stack animates
|
|
153
|
+
* itself. A transition also replaces the toast with a snapshot that answers no hit test, so the
|
|
154
|
+
* close and action buttons of the frontmost toast are unreachable for as long as it runs. Neither
|
|
155
|
+
* shows up as dropped frames; both are plainly visible to someone using the thing.
|
|
156
|
+
*
|
|
157
|
+
* It stays because it is the only way to get a genuine cross-fade between two different toasts,
|
|
158
|
+
* and because a caller who wants it should not have to rebuild the chain below. Each toast names
|
|
159
|
+
* itself for the transition through `view-transition-name`, which is what makes the capture
|
|
160
|
+
* per-toast; without it the document root would be the only captured element and every toast
|
|
161
|
+
* would cross-fade the whole page.
|
|
119
162
|
*
|
|
120
163
|
* The View Transitions API allows one active transition per document: starting a second while the
|
|
121
164
|
* first is still animating aborts the first, which surfaces as a rejection on `ready`. Each new
|
|
@@ -128,7 +171,7 @@ var ToastQueue = class {
|
|
|
128
171
|
* regions on a page, a burst of toasts in one of them puts every other region's toast behind the
|
|
129
172
|
* whole burst, so a toast added elsewhere does not appear until seconds later. Independent chains
|
|
130
173
|
* let a second region interrupt instead — the superseded transition is skipped, which the catch
|
|
131
|
-
* below already handles, which is why
|
|
174
|
+
* below already handles, which is why a chain belongs to one queue and is built per caller.
|
|
132
175
|
*/
|
|
133
176
|
var createViewTransitionUpdate = () => {
|
|
134
177
|
let chain = Promise.resolve();
|
|
@@ -185,17 +228,19 @@ var useToastQueue = (queue) => {
|
|
|
185
228
|
*/
|
|
186
229
|
var resolve = (value, input) => typeof value === "function" ? value(input) : value;
|
|
187
230
|
var createToastFunction = (queue) => {
|
|
188
|
-
const
|
|
231
|
+
const contentOf = (message, options) => ({
|
|
189
232
|
actionProps: options.actionProps,
|
|
190
233
|
description: options.description,
|
|
191
234
|
indicator: options.indicator,
|
|
192
235
|
isLoading: options.isLoading,
|
|
193
236
|
title: message,
|
|
194
237
|
variant: options.variant ?? "default"
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
238
|
+
});
|
|
239
|
+
const deferred = (onClose) => onClose ? () => {
|
|
240
|
+
requestAnimationFrame(() => onClose());
|
|
241
|
+
} : void 0;
|
|
242
|
+
const add = (message, options = {}) => queue.add(contentOf(message, options), {
|
|
243
|
+
onClose: deferred(options.onClose),
|
|
199
244
|
timeout: options.timeout
|
|
200
245
|
});
|
|
201
246
|
const withVariant = (variant) => (message, options = {}) => add(message, {
|
|
@@ -212,6 +257,14 @@ var createToastFunction = (queue) => {
|
|
|
212
257
|
toastFn.resumeAll = () => queue.resumeAll();
|
|
213
258
|
toastFn.success = withVariant("success");
|
|
214
259
|
toastFn.warning = withVariant("warning");
|
|
260
|
+
toastFn.update = (key, message, options = {}) => {
|
|
261
|
+
const inherited = {
|
|
262
|
+
..."onClose" in options ? { onClose: deferred(options.onClose) } : null,
|
|
263
|
+
..."timeout" in options ? { timeout: options.timeout } : null
|
|
264
|
+
};
|
|
265
|
+
if (queue.update(key, contentOf(message, options), inherited)) return key;
|
|
266
|
+
return add(message, options);
|
|
267
|
+
};
|
|
215
268
|
toastFn.promise = (promise, options) => {
|
|
216
269
|
const pending = typeof promise === "function" ? promise() : promise;
|
|
217
270
|
const loadingKey = queue.add({
|
|
@@ -220,11 +273,17 @@ var createToastFunction = (queue) => {
|
|
|
220
273
|
variant: "default"
|
|
221
274
|
}, { timeout: 0 });
|
|
222
275
|
pending.then((data) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
276
|
+
toastFn.update(loadingKey, resolve(options.success, data), {
|
|
277
|
+
isLoading: false,
|
|
278
|
+
timeout: DEFAULT_TOAST_TIMEOUT,
|
|
279
|
+
variant: "success"
|
|
280
|
+
});
|
|
281
|
+
}).catch((error) => {
|
|
282
|
+
toastFn.update(loadingKey, resolve(options.error, error), {
|
|
283
|
+
isLoading: false,
|
|
284
|
+
timeout: DEFAULT_TOAST_TIMEOUT,
|
|
285
|
+
variant: "danger"
|
|
286
|
+
});
|
|
228
287
|
});
|
|
229
288
|
return loadingKey;
|
|
230
289
|
};
|
|
@@ -240,10 +299,9 @@ var toastQueue = new ToastQueue();
|
|
|
240
299
|
var toast = createToastFunction(toastQueue);
|
|
241
300
|
/** How many toasts the default queue holds. For a test asserting it did not leak into the next. */
|
|
242
301
|
var getQueuedToastCount = () => toastQueue.visibleToasts.length;
|
|
243
|
-
/** Empties the default queue
|
|
302
|
+
/** Empties the default queue. For a test, since the queue outlives a mount. */
|
|
244
303
|
var resetToastQueue = () => {
|
|
245
304
|
toastQueue.clear();
|
|
246
|
-
toastQueue.resetTransitions();
|
|
247
305
|
};
|
|
248
306
|
//#endregion
|
|
249
|
-
export { Timer, ToastQueue, createToastFunction, getQueuedToastCount, resetToastQueue, toast, toastQueue, useToastQueue };
|
|
307
|
+
export { Timer, ToastQueue, createToastFunction, createViewTransitionUpdate, getQueuedToastCount, resetToastQueue, toast, toastQueue, useToastQueue };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { dataAttr } from "../../utils/assertion.js";
|
|
2
2
|
import { provideSurfaceContext } from "../surface/surface.context.js";
|
|
3
|
+
import { createAnimationSettleWaiter } from "../../utils/animation-settled.js";
|
|
3
4
|
import { provideToastItemContext, useToastRegionContext } from "./toast.context.js";
|
|
4
5
|
import { useMeasuredHeight } from "../../composables/use-measured-height.js";
|
|
5
6
|
import { useToast } from "../../composables/use-toast.js";
|
|
6
|
-
import { computed, createSlot, defineVaporComponent, onScopeDispose, renderEffect, setDynamicProps, setInsertionState, setTemplateRefBinding, shallowRef, template, unref, watch } from "vue";
|
|
7
|
+
import { computed, createSlot, defineVaporComponent, onMounted, onScopeDispose, renderEffect, setDynamicProps, setInsertionState, setTemplateRefBinding, shallowRef, template, unref, watch } from "vue";
|
|
7
8
|
//#region src/components/toast/toast-root.vue?vue&type=script&setup=true&vapor=true&lang.ts
|
|
8
9
|
var t0 = template("<div>", 1);
|
|
9
10
|
var toast_root_vue_vue_type_script_setup_true_vapor_true_lang_default = /*@__PURE__*/ defineVaporComponent({
|
|
@@ -24,10 +25,53 @@ var toast_root_vue_vue_type_script_setup_true_vapor_true_lang_default = /*@__PUR
|
|
|
24
25
|
};
|
|
25
26
|
const placement = computed(() => props.placement ?? region.placement.value);
|
|
26
27
|
const scaleFactor = computed(() => props.scaleFactor ?? region.scaleFactor.value);
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const isExiting = computed(() => region.exitingKeys.value.has(props.toast.key));
|
|
29
|
+
/**
|
|
30
|
+
* The place this toast holds in the stack. Index 0 is the newest, which is the one in front.
|
|
31
|
+
*
|
|
32
|
+
* A toast on its way out keeps the slot it had while the ones behind it renumber and close up,
|
|
33
|
+
* so the stack reflows around it instead of waiting for it to finish.
|
|
34
|
+
*/
|
|
35
|
+
const lastIndex = shallowRef(0);
|
|
36
|
+
const index = computed(() => {
|
|
37
|
+
if (isExiting.value) return lastIndex.value;
|
|
38
|
+
return Math.max(region.visibleToasts.value.findIndex((entry) => entry.key === props.toast.key), 0);
|
|
39
|
+
});
|
|
40
|
+
watch(index, (next) => {
|
|
41
|
+
if (!isExiting.value) lastIndex.value = next;
|
|
42
|
+
}, { immediate: true });
|
|
29
43
|
const isFrontmost = computed(() => index.value <= 0);
|
|
30
|
-
const isHidden = computed(() => index.value >= region.maxVisibleToasts.value);
|
|
44
|
+
const isHidden = computed(() => !isExiting.value && index.value >= region.maxVisibleToasts.value);
|
|
45
|
+
/**
|
|
46
|
+
* Rendered from the very first frame rather than set once mounted.
|
|
47
|
+
*
|
|
48
|
+
* A transition needs a style to leave, so the offset has to be the element's *first* resolved
|
|
49
|
+
* style. Written after the element already exists, the browser transitions towards the offset and
|
|
50
|
+
* the flip below then drags it back — which reads as a toast that twitches instead of arriving.
|
|
51
|
+
*/
|
|
52
|
+
const isEntering = shallowRef(true);
|
|
53
|
+
onMounted(() => {
|
|
54
|
+
let inner = 0;
|
|
55
|
+
const outer = requestAnimationFrame(() => {
|
|
56
|
+
inner = requestAnimationFrame(() => {
|
|
57
|
+
isEntering.value = false;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
onScopeDispose(() => {
|
|
61
|
+
cancelAnimationFrame(outer);
|
|
62
|
+
cancelAnimationFrame(inner);
|
|
63
|
+
}, true);
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* The exit is waited for rather than timed, so its length lives in the stylesheet — the one place
|
|
67
|
+
* that knows it — and no number here can fall out of step with it.
|
|
68
|
+
*/
|
|
69
|
+
const exit = createAnimationSettleWaiter(() => element.value);
|
|
70
|
+
watch(isExiting, (exiting) => {
|
|
71
|
+
if (!exiting) return;
|
|
72
|
+
exit.whenSettled(() => region.onExitFinished(props.toast.key));
|
|
73
|
+
});
|
|
74
|
+
onScopeDispose(exit.cancel, true);
|
|
31
75
|
const { height } = useMeasuredHeight(element);
|
|
32
76
|
/**
|
|
33
77
|
* Every toast reports its height up, because a toast that is *not* frontmost is clipped to the
|
|
@@ -64,32 +108,41 @@ var toast_root_vue_vue_type_script_setup_true_vapor_true_lang_default = /*@__PUR
|
|
|
64
108
|
}));
|
|
65
109
|
const style = computed(() => {
|
|
66
110
|
const visible = region.visibleToasts.value;
|
|
111
|
+
const heights = region.heightsByKey.value;
|
|
67
112
|
const frontKey = visible[0]?.key;
|
|
68
|
-
const frontHeight = (frontKey === void 0 ? void 0 :
|
|
69
|
-
|
|
70
|
-
|
|
113
|
+
const frontHeight = (frontKey === void 0 ? void 0 : heights[frontKey]) ?? height.value ?? 0;
|
|
114
|
+
let heightsBefore = 0;
|
|
115
|
+
for (let position = 0; position < index.value; position += 1) {
|
|
116
|
+
const key = visible[position]?.key;
|
|
117
|
+
heightsBefore += (key === void 0 ? void 0 : heights[key]) ?? frontHeight;
|
|
118
|
+
}
|
|
119
|
+
const gap = index.value * region.gap.value;
|
|
71
120
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
121
|
+
...frontHeight ? { "--front-height": `${frontHeight}px` } : null,
|
|
122
|
+
"--offset-collapsed": `${gap}px`,
|
|
123
|
+
"--offset-expanded": `${heightsBefore + gap}px`,
|
|
124
|
+
"--scale-collapsed": `${1 - index.value * scaleFactor.value}`,
|
|
76
125
|
viewTransitionName: `rp-toast-${props.toast.key.replace(/[^a-zA-Z0-9]/g, "-")}`,
|
|
77
|
-
zIndex: visible.length - index.value,
|
|
78
|
-
...
|
|
126
|
+
zIndex: isExiting.value ? 0 : visible.length - index.value,
|
|
127
|
+
...height.value === void 0 ? null : { "--toast-height": `${height.value}px` }
|
|
79
128
|
};
|
|
80
129
|
});
|
|
81
130
|
const n1 = t0();
|
|
82
131
|
renderEffect(() => {
|
|
83
132
|
const _dataAttr = unref(dataAttr);
|
|
133
|
+
const _isExiting = isExiting.value;
|
|
84
134
|
const _isFrontmost = isFrontmost.value;
|
|
85
135
|
setDynamicProps(n1, [{
|
|
86
136
|
class: styles.value,
|
|
137
|
+
"data-entering": _dataAttr(isEntering.value),
|
|
138
|
+
"data-exiting": _dataAttr(_isExiting),
|
|
139
|
+
"data-expanded": _dataAttr(unref(region).isExpanded.value),
|
|
87
140
|
"data-frontmost": _dataAttr(_isFrontmost),
|
|
88
141
|
"data-hidden": _dataAttr(isHidden.value),
|
|
89
142
|
"data-index": index.value,
|
|
90
143
|
"data-slot": "toast",
|
|
91
144
|
style: style.value,
|
|
92
|
-
tabindex: _isFrontmost ? 0 : -1
|
|
145
|
+
tabindex: _isFrontmost && !_isExiting ? 0 : -1
|
|
93
146
|
}, unref(api).toastAttrs.value]);
|
|
94
147
|
});
|
|
95
148
|
setInsertionState(n1);
|
|
@@ -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
|
}
|
|
@@ -39,7 +39,9 @@ var useCheckboxGroupState = (options = {}) => {
|
|
|
39
39
|
const isItemRequired = computed(() => isRequired.value && value.value.length === 0);
|
|
40
40
|
const validation = useFormValidationState({
|
|
41
41
|
isInvalid: () => toValue(options.isInvalid),
|
|
42
|
+
isRequired,
|
|
42
43
|
name: () => toValue(options.name),
|
|
44
|
+
requiredControl: "checkbox",
|
|
43
45
|
validate: () => toValue(options.validate),
|
|
44
46
|
validationBehavior: () => toValue(options.validationBehavior),
|
|
45
47
|
value: () => value.value
|
|
@@ -68,6 +68,7 @@ var useColorFieldState = (options = {}) => {
|
|
|
68
68
|
const inputValue = shallowRef(hasInitialValue && colorValue.value ? colorValue.value.toString("hex") : "");
|
|
69
69
|
const validation = useFormValidationState({
|
|
70
70
|
isInvalid: () => toValue(options.isInvalid),
|
|
71
|
+
isRequired: () => toValue(options.isRequired),
|
|
71
72
|
name: () => toValue(options.name),
|
|
72
73
|
validate: () => toValue(options.validate),
|
|
73
74
|
validationBehavior: () => toValue(options.validationBehavior),
|
|
@@ -78,6 +78,8 @@ export interface UseComboBoxStateOptions<T> {
|
|
|
78
78
|
disabledBehavior?: MaybeRefOrGetter<DisabledBehavior | undefined>;
|
|
79
79
|
isReadOnly?: MaybeRefOrGetter<boolean | undefined>;
|
|
80
80
|
isInvalid?: MaybeRefOrGetter<boolean | undefined>;
|
|
81
|
+
/** Whether the field has to hold a value. Enforced by the validation state under `"aria"`. */
|
|
82
|
+
isRequired?: MaybeRefOrGetter<boolean | undefined>;
|
|
81
83
|
validate?: MaybeRefOrGetter<ValidationFunction<ComboBoxValidationValue> | undefined>;
|
|
82
84
|
validationBehavior?: MaybeRefOrGetter<ValidationBehavior | undefined>;
|
|
83
85
|
name?: MaybeRefOrGetter<string | undefined>;
|
|
@@ -2,7 +2,7 @@ import { useControllableState } from "./use-controllable-state.js";
|
|
|
2
2
|
import { useMenuTriggerState } from "./use-overlay-trigger-state.js";
|
|
3
3
|
import { useCollection } from "./use-collection.js";
|
|
4
4
|
import { createListCollection } from "../utils/virtualizer-collection.js";
|
|
5
|
-
import { useFormValidationState } from "./use-form-validation-state.js";
|
|
5
|
+
import { isValueMissing, useFormValidationState } from "./use-form-validation-state.js";
|
|
6
6
|
import { useSelectionManager } from "./use-selection-manager.js";
|
|
7
7
|
import { defaultItemTextValue } from "./use-select-state.js";
|
|
8
8
|
import { useFilter } from "./use-filter.js";
|
|
@@ -137,7 +137,9 @@ var useComboBoxState = (options) => {
|
|
|
137
137
|
});
|
|
138
138
|
const inputValue = computed(() => input.state.value);
|
|
139
139
|
const validation = useFormValidationState({
|
|
140
|
+
isEmpty: (current) => !current || isValueMissing(current.value),
|
|
140
141
|
isInvalid: options.isInvalid,
|
|
142
|
+
isRequired: options.isRequired,
|
|
141
143
|
name: options.name,
|
|
142
144
|
validate: options.validate,
|
|
143
145
|
validationBehavior: options.validationBehavior,
|
|
@@ -170,6 +170,7 @@ var useDateFieldState = (options) => {
|
|
|
170
170
|
const validation = useFormValidationState({
|
|
171
171
|
builtinValidation,
|
|
172
172
|
isInvalid: () => toValue(options.isInvalid),
|
|
173
|
+
isRequired,
|
|
173
174
|
name: () => toValue(options.name),
|
|
174
175
|
validate: () => options.validate,
|
|
175
176
|
validationBehavior: () => toValue(options.validationBehavior),
|
|
@@ -24,6 +24,8 @@ export interface UseDatePickerStateOptions {
|
|
|
24
24
|
hideTimeZone?: MaybeRefOrGetter<boolean | undefined>;
|
|
25
25
|
shouldForceLeadingZeros?: MaybeRefOrGetter<boolean | undefined>;
|
|
26
26
|
isInvalid?: MaybeRefOrGetter<boolean | undefined>;
|
|
27
|
+
/** Whether the field has to hold a value. Enforced by the validation state under `"aria"`. */
|
|
28
|
+
isRequired?: MaybeRefOrGetter<boolean | undefined>;
|
|
27
29
|
validate?: (value: DateValue | null) => string | string[] | true | null | undefined;
|
|
28
30
|
validationBehavior?: MaybeRefOrGetter<ValidationBehavior | undefined>;
|
|
29
31
|
name?: MaybeRefOrGetter<string | undefined>;
|
|
@@ -54,6 +54,7 @@ var useDatePickerState = (options) => {
|
|
|
54
54
|
const validation = useFormValidationState({
|
|
55
55
|
builtinValidation: computed(() => getDateValidationResult(value.value, toValue(options.minValue), toValue(options.maxValue), options.isDateUnavailable, formatOpts.value)),
|
|
56
56
|
isInvalid: () => toValue(options.isInvalid),
|
|
57
|
+
isRequired: () => toValue(options.isRequired),
|
|
57
58
|
name: () => toValue(options.name),
|
|
58
59
|
validate: () => options.validate,
|
|
59
60
|
validationBehavior: () => toValue(options.validationBehavior),
|
|
@@ -35,6 +35,8 @@ export interface UseDateRangePickerStateOptions {
|
|
|
35
35
|
hideTimeZone?: MaybeRefOrGetter<boolean | undefined>;
|
|
36
36
|
shouldForceLeadingZeros?: MaybeRefOrGetter<boolean | undefined>;
|
|
37
37
|
isInvalid?: MaybeRefOrGetter<boolean | undefined>;
|
|
38
|
+
/** Whether the field has to hold a value. Enforced by the validation state under `"aria"`. */
|
|
39
|
+
isRequired?: MaybeRefOrGetter<boolean | undefined>;
|
|
38
40
|
validate?: (value: DateRange | null) => string | string[] | true | null | undefined;
|
|
39
41
|
validationBehavior?: MaybeRefOrGetter<ValidationBehavior | undefined>;
|
|
40
42
|
/** Name the start of the range is submitted under. */
|
|
@@ -78,7 +78,9 @@ var useDateRangePickerState = (options) => {
|
|
|
78
78
|
}));
|
|
79
79
|
const validation = useFormValidationState({
|
|
80
80
|
builtinValidation: computed(() => getRangeValidationResult(value.value, toValue(options.minValue), toValue(options.maxValue), options.isDateUnavailable, formatOpts.value)),
|
|
81
|
+
isEmpty: (range) => !range?.start || !range.end,
|
|
81
82
|
isInvalid: () => toValue(options.isInvalid),
|
|
83
|
+
isRequired: () => toValue(options.isRequired),
|
|
82
84
|
name: () => [toValue(options.startName), toValue(options.endName)].filter(Boolean),
|
|
83
85
|
validate: () => options.validate,
|
|
84
86
|
validationBehavior: () => toValue(options.validationBehavior),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComputedRef, MaybeRefOrGetter } from "vue";
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter, Ref } from "vue";
|
|
2
2
|
/**
|
|
3
3
|
* Snapshot of an element's `ValidityState`.
|
|
4
4
|
*
|
|
@@ -39,6 +39,8 @@ export type ValidationFunction<T> = (value: T) => string | string[] | true | nul
|
|
|
39
39
|
export declare const VALID_VALIDITY_STATE: ValidationDetails;
|
|
40
40
|
/** Failure that came from a prop, a `validate` function or the server rather than the browser. */
|
|
41
41
|
export declare const CUSTOM_VALIDITY_STATE: ValidationDetails;
|
|
42
|
+
/** A required field with nothing in it. What the browser reports under `"native"`. */
|
|
43
|
+
export declare const MISSING_VALIDITY_STATE: ValidationDetails;
|
|
42
44
|
export declare const DEFAULT_VALIDATION_RESULT: ValidationResult;
|
|
43
45
|
/**
|
|
44
46
|
* One verdict out of several, for a control whose value has more than one part.
|
|
@@ -51,6 +53,23 @@ export declare const mergeValidation: (...results: ValidationResult[]) => Valida
|
|
|
51
53
|
export type ValidatableElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
52
54
|
/** Freeze an element's live validity into a result the state layer can hold. */
|
|
53
55
|
export declare const getNativeValidation: (element: ValidatableElement) => ValidationResult;
|
|
56
|
+
/**
|
|
57
|
+
* The kinds of control a required field can be. Each is a different sentence: a list asks to be
|
|
58
|
+
* chosen from, a checkbox to be ticked.
|
|
59
|
+
*/
|
|
60
|
+
export type RequiredControl = "checkbox" | "radio" | "select" | "text";
|
|
61
|
+
/**
|
|
62
|
+
* What the browser would say about an empty control of this kind.
|
|
63
|
+
*
|
|
64
|
+
* Read off a detached probe rather than translated here. The platform already holds the sentence,
|
|
65
|
+
* in the *browser's* locale — which is the locale a validation message belongs in, since it sits
|
|
66
|
+
* beside the browser's own — and it is the one the same field would report under `"native"`.
|
|
67
|
+
*
|
|
68
|
+
* Empty on a server, where the field cannot have been revealed yet and so has nothing to say.
|
|
69
|
+
*/
|
|
70
|
+
export declare const missingValueMessage: (kind: RequiredControl) => string;
|
|
71
|
+
/** Whether a value counts as nothing. Covers every shape a field holds when it is empty. */
|
|
72
|
+
export declare const isValueMissing: (value: unknown) => boolean;
|
|
54
73
|
/** Errors a server returned, keyed by the `name` each field submits under. */
|
|
55
74
|
export type FormValidationErrors = Record<string, string | string[]>;
|
|
56
75
|
export interface FormContext {
|
|
@@ -58,6 +77,14 @@ export interface FormContext {
|
|
|
58
77
|
validationErrors: ComputedRef<FormValidationErrors>;
|
|
59
78
|
/** Default for every field inside, unless the field names its own. */
|
|
60
79
|
validationBehavior: ComputedRef<ValidationBehavior>;
|
|
80
|
+
/**
|
|
81
|
+
* Bumped by every submit attempt.
|
|
82
|
+
*
|
|
83
|
+
* Under `"native"` a field learns of a failed submit from the browser, which fires `invalid` at
|
|
84
|
+
* it. Under `"aria"` the browser is not involved, so this is the only thing that tells a field
|
|
85
|
+
* holding an unrevealed error that it is now being asked for.
|
|
86
|
+
*/
|
|
87
|
+
submitCount?: Readonly<Ref<number>>;
|
|
61
88
|
}
|
|
62
89
|
/**
|
|
63
90
|
* React splits this in two — `FormValidationContext` in react-stately for the errors,
|
|
@@ -83,6 +110,18 @@ export interface UseFormValidationStateOptions<T> {
|
|
|
83
110
|
name?: MaybeRefOrGetter<string | string[] | undefined>;
|
|
84
111
|
/** Validity a composite field worked out from its own parts. */
|
|
85
112
|
builtinValidation?: MaybeRefOrGetter<ValidationResult | undefined>;
|
|
113
|
+
/**
|
|
114
|
+
* Whether the field has to hold a value.
|
|
115
|
+
*
|
|
116
|
+
* Enforced here only under `"aria"`. Under `"native"` the control carries the `required`
|
|
117
|
+
* attribute and the browser reaches the same verdict itself, so a second one here would only
|
|
118
|
+
* be a chance to disagree.
|
|
119
|
+
*/
|
|
120
|
+
isRequired?: MaybeRefOrGetter<boolean | undefined>;
|
|
121
|
+
/** Which sentence a missing value is reported with. @default "text" */
|
|
122
|
+
requiredControl?: RequiredControl;
|
|
123
|
+
/** Whether the value counts as nothing. @default {@link isValueMissing} */
|
|
124
|
+
isEmpty?: (value: T | null | undefined) => boolean;
|
|
86
125
|
/**
|
|
87
126
|
* A validation state owned by something above, which this field reports through instead of
|
|
88
127
|
* keeping one of its own.
|
|
@@ -118,15 +157,19 @@ export declare const isEqualValidation: (a: ValidationResult | null, b: Validati
|
|
|
118
157
|
* Decide what a field's validation currently says, ported from React Aria's
|
|
119
158
|
* `packages/react-stately/src/form/useFormValidationState.ts` (react-stately 3.49.0).
|
|
120
159
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
160
|
+
* Holds no element of its own — `useFormValidation` is what connects this to a real input.
|
|
161
|
+
* Five sources feed it, in a fixed order of precedence:
|
|
123
162
|
*
|
|
124
163
|
* ```
|
|
125
|
-
* realtime = controlled ?? server ?? client ?? builtin ?? valid
|
|
164
|
+
* realtime = controlled ?? server ?? client ?? required ?? builtin ?? valid
|
|
126
165
|
* display(native) = controlled ?? server ?? committed
|
|
127
|
-
* display(aria) = controlled ?? server ?? client ?? builtin ?? committed
|
|
166
|
+
* display(aria) = controlled ?? server ?? client ?? required* ?? builtin ?? committed
|
|
128
167
|
* ```
|
|
129
168
|
*
|
|
169
|
+
* `required` is the missing-value rule, and it is there for `"aria"` alone: under `"native"` the
|
|
170
|
+
* control carries the attribute and the browser reaches the verdict itself. The `*` is the reveal
|
|
171
|
+
* — it waits for a commit or a submit, so a form does not arrive with every required field red.
|
|
172
|
+
*
|
|
130
173
|
* `realtimeValidation` is what the field pushes onto the input through `setCustomValidity`,
|
|
131
174
|
* so the browser blocks submission the moment the value stops being acceptable.
|
|
132
175
|
* `displayValidation` is what the user reads, and under `"native"` it moves only on commit —
|