ropav 0.13.0 → 0.15.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.
Files changed (118) hide show
  1. package/dist/components/accordion/index.d.ts +1 -0
  2. package/dist/components/alert-dialog/alert-dialog-backdrop.js +3 -1
  3. package/dist/components/autocomplete/index.d.ts +1 -0
  4. package/dist/components/calendar/index.d.ts +1 -0
  5. package/dist/components/checkbox-group/index.d.ts +1 -0
  6. package/dist/components/color-slider/index.d.ts +1 -0
  7. package/dist/components/combo-box/index.d.ts +2 -0
  8. package/dist/components/date-field/index.d.ts +1 -0
  9. package/dist/components/date-picker/index.d.ts +2 -0
  10. package/dist/components/date-range-picker/index.d.ts +2 -0
  11. package/dist/components/date-range-picker/use-date-range-picker.js +1 -1
  12. package/dist/components/disclosure-group/index.d.ts +1 -0
  13. package/dist/components/drawer/drawer-backdrop.js +3 -1
  14. package/dist/components/dropdown/dropdown-menu.js +7 -3
  15. package/dist/components/dropdown/dropdown-root.js +1 -1
  16. package/dist/components/dropdown/dropdown-submenu-trigger.js +1 -1
  17. package/dist/components/dropdown/dropdown.context.d.ts +1 -1
  18. package/dist/components/dropdown/index.d.ts +1 -0
  19. package/dist/components/field-error/field-error-root.js +1 -1
  20. package/dist/components/form/form-root.js +1 -1
  21. package/dist/components/index.d.ts +1 -0
  22. package/dist/components/meter/meter-root.js +1 -1
  23. package/dist/components/modal/modal-backdrop.js +3 -1
  24. package/dist/components/number-field/index.d.ts +1 -0
  25. package/dist/components/overlay/overlay-popover.js +15 -6
  26. package/dist/components/portal-provider/index.d.ts +3 -0
  27. package/dist/components/portal-provider/index.js +2 -0
  28. package/dist/components/portal-provider/portal-provider.d.ts +21 -0
  29. package/dist/components/portal-provider/portal-provider.js +20 -0
  30. package/dist/components/portal-provider/portal-provider.types.d.ts +22 -0
  31. package/dist/components/radio-group/index.d.ts +1 -0
  32. package/dist/components/range-calendar/index.d.ts +1 -0
  33. package/dist/components/segmented-control/index.d.ts +1 -0
  34. package/dist/components/select/index.d.ts +1 -0
  35. package/dist/components/slider/index.d.ts +1 -0
  36. package/dist/components/table/table-drag-handle.js +1 -1
  37. package/dist/components/tabs/index.d.ts +1 -0
  38. package/dist/components/tabs/tabs-root.js +5 -0
  39. package/dist/components/tabs/tabs.types.d.ts +8 -0
  40. package/dist/components/time-field/index.d.ts +2 -0
  41. package/dist/components/toast/toast-provider.js +3 -1
  42. package/dist/components/toggle-button/index.d.ts +1 -0
  43. package/dist/components/toggle-button-group/index.d.ts +1 -0
  44. package/dist/components/tooltip/index.d.ts +1 -0
  45. package/dist/components/tooltip/tooltip-content.js +4 -1
  46. package/dist/composables/drag-manager.js +1 -1
  47. package/dist/composables/drag-modality.js +2 -1
  48. package/dist/composables/focus-scope-registry.d.ts +27 -0
  49. package/dist/composables/focus-scope-registry.js +45 -0
  50. package/dist/composables/form-context.d.ts +25 -0
  51. package/dist/composables/form-context.js +17 -0
  52. package/dist/composables/index.d.ts +1 -24
  53. package/dist/composables/interaction-modality.d.ts +42 -0
  54. package/dist/composables/interaction-modality.js +91 -0
  55. package/dist/composables/use-calendar-cell.js +1 -1
  56. package/dist/composables/use-checkbox-group-state.js +2 -1
  57. package/dist/composables/use-combo-box-state.d.ts +2 -1
  58. package/dist/composables/use-combo-box-state.js +3 -2
  59. package/dist/composables/use-combo-box.js +6 -3
  60. package/dist/composables/use-css-variable.js +1 -5
  61. package/dist/composables/use-default-locale.d.ts +9 -0
  62. package/dist/composables/use-default-locale.js +39 -0
  63. package/dist/composables/use-dismissable.d.ts +6 -0
  64. package/dist/composables/use-dismissable.js +9 -11
  65. package/dist/composables/use-display-names.js +1 -1
  66. package/dist/composables/use-drag.js +1 -1
  67. package/dist/composables/use-droppable-collection.js +1 -1
  68. package/dist/composables/use-focus-scope.d.ts +6 -12
  69. package/dist/composables/use-focus-scope.js +14 -33
  70. package/dist/composables/use-form-validation-state.d.ts +1 -60
  71. package/dist/composables/use-form-validation-state.js +3 -134
  72. package/dist/composables/use-form-validation.d.ts +2 -1
  73. package/dist/composables/use-form-validation.js +2 -2
  74. package/dist/composables/use-interaction-states.d.ts +0 -36
  75. package/dist/composables/use-interaction-states.js +8 -91
  76. package/dist/composables/use-locale.d.ts +0 -14
  77. package/dist/composables/use-locale.js +4 -37
  78. package/dist/composables/use-localized-string-dictionary.d.ts +10 -0
  79. package/dist/composables/use-localized-string-dictionary.js +26 -0
  80. package/dist/composables/use-localized-string-formatter.d.ts +0 -9
  81. package/dist/composables/use-localized-string-formatter.js +3 -24
  82. package/dist/composables/use-long-press.d.ts +0 -2
  83. package/dist/composables/use-long-press.js +2 -2
  84. package/dist/composables/use-menu-trigger-state.d.ts +64 -0
  85. package/dist/composables/use-menu-trigger-state.js +90 -0
  86. package/dist/composables/use-menu-trigger.d.ts +2 -1
  87. package/dist/composables/use-modal-overlay.d.ts +2 -1
  88. package/dist/composables/use-modal-overlay.js +17 -4
  89. package/dist/composables/use-overlay-trigger-state.d.ts +0 -62
  90. package/dist/composables/use-overlay-trigger-state.js +2 -87
  91. package/dist/composables/use-page-size.d.ts +14 -0
  92. package/dist/composables/use-page-size.js +42 -0
  93. package/dist/composables/use-portal.d.ts +35 -0
  94. package/dist/composables/use-portal.js +47 -0
  95. package/dist/composables/use-press.d.ts +0 -20
  96. package/dist/composables/use-press.js +3 -26
  97. package/dist/composables/use-range-calendar.js +1 -1
  98. package/dist/composables/use-select-state.d.ts +1 -1
  99. package/dist/composables/use-select-state.js +1 -1
  100. package/dist/composables/use-slider.js +1 -1
  101. package/dist/composables/use-toast-region.js +2 -1
  102. package/dist/composables/use-tooltip-trigger-state.js +1 -15
  103. package/dist/composables/use-tooltip-trigger.js +1 -1
  104. package/dist/composables/use-viewport-size.d.ts +0 -13
  105. package/dist/composables/use-viewport-size.js +1 -38
  106. package/dist/composables/validation-result.d.ts +37 -0
  107. package/dist/composables/validation-result.js +122 -0
  108. package/dist/composables/virtual-event.d.ts +20 -0
  109. package/dist/composables/virtual-event.js +28 -0
  110. package/dist/index.js +13 -35
  111. package/dist/ropav.min.css +1 -1
  112. package/dist/utils/aria-hide-outside.d.ts +4 -1
  113. package/dist/utils/aria-hide-outside.js +17 -5
  114. package/dist/utils/date-validation.js +1 -1
  115. package/dist/utils/region.d.ts +10 -0
  116. package/dist/utils/region.js +13 -0
  117. package/dist/version.js +1 -1
  118. package/package.json +6 -2
@@ -1,4 +1,5 @@
1
- import { getInteractionModality, useInteractionModality } from "./use-interaction-states.js";
1
+ import { getInteractionModality } from "./interaction-modality.js";
2
+ import { useInteractionModality } from "./use-interaction-states.js";
2
3
  import { computed } from "vue";
3
4
  //#region src/composables/drag-modality.ts
4
5
  var mapModality = (modality) => {
@@ -0,0 +1,27 @@
1
+ export interface RegisteredScope {
2
+ root: () => HTMLElement | null;
3
+ contain: () => boolean;
4
+ /** The part of the page the scope belongs to, `null` for the whole document. */
5
+ region: () => Element | null;
6
+ }
7
+ /**
8
+ * The innermost scope holding focus in a region, among the scopes whose region overlaps it.
9
+ *
10
+ * Without a region every scope overlaps every other, which is the page-wide question. With one, a
11
+ * modal in one app's window is not shadowed by a modal another app opened later in its own.
12
+ */
13
+ export declare const innermostContainingScope: (region?: Element | null) => RegisteredScope | null;
14
+ /**
15
+ * Whether the element sits inside any registered scope.
16
+ *
17
+ * Any scope rather than only the descendants of one, because an overlay opened from inside
18
+ * another is a **sibling** in the DOM rather than a descendant — a submenu renders into its root
19
+ * popover's container, and a dropdown opened from a popover makes a container of its own. There
20
+ * is no tree to walk here, so a scope boundary is the thing that answers "focus is still in an
21
+ * overlay". React Aria walks its scope tree and asks the narrower question; the difference shows
22
+ * only when two unrelated overlays are open at once and focus moves between them, where this
23
+ * errs towards leaving them open.
24
+ */
25
+ export declare const isElementInAnyFocusScope: (element: Element) => boolean;
26
+ /** Add a scope as the innermost, returning its removal. */
27
+ export declare const registerFocusScope: (scope: RegisteredScope) => (() => void);
@@ -0,0 +1,45 @@
1
+ import { regionsOverlap } from "../utils/region.js";
2
+ //#region src/composables/focus-scope-registry.ts
3
+ /**
4
+ * Registered scopes, outermost first.
5
+ *
6
+ * Module-level because containment is a question about the whole page, not about one overlay:
7
+ * only the innermost containing scope may hold focus, and focus moving into a scope nested
8
+ * inside it has to be allowed through rather than pulled back.
9
+ */
10
+ var scopes = [];
11
+ /**
12
+ * The innermost scope holding focus in a region, among the scopes whose region overlaps it.
13
+ *
14
+ * Without a region every scope overlaps every other, which is the page-wide question. With one, a
15
+ * modal in one app's window is not shadowed by a modal another app opened later in its own.
16
+ */
17
+ var innermostContainingScope = (region = null) => {
18
+ for (let index = scopes.length - 1; index >= 0; index--) {
19
+ const scope = scopes[index];
20
+ if (scope.contain() && regionsOverlap(scope.region(), region)) return scope;
21
+ }
22
+ return null;
23
+ };
24
+ /**
25
+ * Whether the element sits inside any registered scope.
26
+ *
27
+ * Any scope rather than only the descendants of one, because an overlay opened from inside
28
+ * another is a **sibling** in the DOM rather than a descendant — a submenu renders into its root
29
+ * popover's container, and a dropdown opened from a popover makes a container of its own. There
30
+ * is no tree to walk here, so a scope boundary is the thing that answers "focus is still in an
31
+ * overlay". React Aria walks its scope tree and asks the narrower question; the difference shows
32
+ * only when two unrelated overlays are open at once and focus moves between them, where this
33
+ * errs towards leaving them open.
34
+ */
35
+ var isElementInAnyFocusScope = (element) => scopes.some((scope) => scope.root()?.contains(element));
36
+ /** Add a scope as the innermost, returning its removal. */
37
+ var registerFocusScope = (scope) => {
38
+ scopes.push(scope);
39
+ return () => {
40
+ const index = scopes.indexOf(scope);
41
+ if (index >= 0) scopes.splice(index, 1);
42
+ };
43
+ };
44
+ //#endregion
45
+ export { innermostContainingScope, isElementInAnyFocusScope, registerFocusScope };
@@ -0,0 +1,25 @@
1
+ import type { FormValidationErrors, ValidationBehavior } from "./use-form-validation-state.js";
2
+ import type { ComputedRef, Ref } from "vue";
3
+ export interface FormContext {
4
+ /** Errors keyed by field `name`, shown until the user edits the value. */
5
+ validationErrors: ComputedRef<FormValidationErrors>;
6
+ /** Default for every field inside, unless the field names its own. */
7
+ validationBehavior: ComputedRef<ValidationBehavior>;
8
+ /**
9
+ * Bumped by every submit attempt.
10
+ *
11
+ * Under `"native"` a field learns of a failed submit from the browser, which fires `invalid` at
12
+ * it. Under `"aria"` the browser is not involved, so this is the only thing that tells a field
13
+ * holding an unrevealed error that it is now being asked for.
14
+ */
15
+ submitCount?: Readonly<Ref<number>>;
16
+ }
17
+ /**
18
+ * React splits this in two — `FormValidationContext` in react-stately for the errors,
19
+ * `FormContext` in react-aria-components for the behaviour — only because the two live in
20
+ * different packages. Here one provider hands out both, so a field injects once and the two
21
+ * halves cannot drift apart.
22
+ *
23
+ * Loose: a field outside a form is the normal case, not an error.
24
+ */
25
+ export declare const useFormContext: () => FormContext | null, provideFormContext: (value: FormContext | null) => FormContext | null;
@@ -0,0 +1,17 @@
1
+ import { createContext } from "../utils/create-context.js";
2
+ //#region src/composables/form-context.ts
3
+ /**
4
+ * React splits this in two — `FormValidationContext` in react-stately for the errors,
5
+ * `FormContext` in react-aria-components for the behaviour — only because the two live in
6
+ * different packages. Here one provider hands out both, so a field injects once and the two
7
+ * halves cannot drift apart.
8
+ *
9
+ * Loose: a field outside a form is the normal case, not an error.
10
+ */
11
+ var [useFormContext, provideFormContext] = createContext({
12
+ defaultValue: null,
13
+ name: "FormContext",
14
+ strict: false
15
+ });
16
+ //#endregion
17
+ export { provideFormContext, useFormContext };
@@ -1,23 +1,10 @@
1
1
  export * from "./focus-responder.js";
2
2
  export * from "./press-responder.js";
3
- export * from "./use-calendar-state.js";
4
- export * from "./use-checkbox-group-state.js";
5
3
  export * from "./use-collator.js";
6
4
  export * from "./use-collection.js";
7
- export * from "./use-color-area-state.js";
8
- export * from "./use-color-channel-field-state.js";
9
- export * from "./use-color-field-state.js";
10
- export * from "./use-color-picker-state.js";
11
- export * from "./use-color-slider-state.js";
12
- export * from "./use-combo-box-state.js";
13
5
  export * from "./use-controllable-state.js";
14
- export * from "./use-css-variable.js";
15
- export * from "./use-date-field-state.js";
16
6
  export * from "./use-date-formatter.js";
17
- export * from "./use-date-picker-state.js";
18
- export * from "./use-date-range-picker-state.js";
19
7
  export * from "./use-description.js";
20
- export * from "./use-disclosure-group.js";
21
8
  export * from "./use-dismissable.js";
22
9
  export * from "./use-drag-and-drop.js";
23
10
  export * from "./use-drop.js";
@@ -29,7 +16,6 @@ export * from "./use-form-reset.js";
29
16
  export * from "./use-form-validation-state.js";
30
17
  export * from "./use-id.js";
31
18
  export * from "./use-interaction-states.js";
32
- export * from "./use-labels.js";
33
19
  export * from "./use-list-data.js";
34
20
  export * from "./use-list-keyboard.js";
35
21
  export * from "./use-locale.js";
@@ -37,22 +23,13 @@ export * from "./use-localized-string-formatter.js";
37
23
  export * from "./use-long-press.js";
38
24
  export * from "./use-media-query.js";
39
25
  export * from "./use-move.js";
40
- export * from "./use-number-field-state.js";
41
26
  export * from "./use-number-formatter.js";
42
27
  export * from "./use-overlay-position.js";
43
28
  export * from "./use-overlay-trigger-state.js";
29
+ export * from "./use-portal.js";
44
30
  export * from "./use-press.js";
45
31
  export * from "./use-prevent-scroll.js";
46
- export * from "./use-radio-group-state.js";
47
- export * from "./use-range-calendar-state.js";
48
- export * from "./use-select-state.js";
49
32
  export * from "./use-selection-manager.js";
50
- export * from "./use-single-select-list-state.js";
51
- export * from "./use-slider-state.js";
52
- export * from "./use-tab-list-state.js";
53
- export * from "./use-time-field-state.js";
54
33
  export * from "./use-toast.js";
55
- export * from "./use-toggle-group-state.js";
56
- export * from "./use-tooltip-trigger-state.js";
57
34
  export * from "./use-typeahead.js";
58
35
  export * from "./use-viewport-size.js";
@@ -0,0 +1,42 @@
1
+ import type { InteractionModality } from "./use-interaction-states.js";
2
+ /**
3
+ * Keep the shared modality listeners attached, returning the release.
4
+ *
5
+ * For a component that has to ask how the user is driving the page without otherwise taking part
6
+ * in the interaction lifecycle — a tooltip trigger, which opens on hover only for a real pointer.
7
+ * Without this the answer would be whatever it was when the last interactive component unmounted.
8
+ */
9
+ export declare const retainInteractionModality: () => (() => void);
10
+ /**
11
+ * How the user is driving the page right now.
12
+ *
13
+ * Ported from React Aria's `getInteractionModality`. Not reactive on purpose — read it inside an
14
+ * event handler, where the answer is the one that matters.
15
+ */
16
+ export declare const getInteractionModality: () => InteractionModality;
17
+ /**
18
+ * Whether focus arriving right now is the kind that came from a keyboard.
19
+ *
20
+ * Ported from React Aria's `isFocusVisible`, and reads the same answer React Aria reads — the one
21
+ * that follows the pointer as it moves. Read it inside a handler: a tooltip asks it on focus to
22
+ * tell tabbing to a button apart from clicking it, and only the pointer-following answer knows the
23
+ * user had already reached for the mouse.
24
+ *
25
+ * Not the answer that decides whether a ring is painted. That one lives on `useInteractionStates`
26
+ * and deliberately ignores a bare mouse move, so moving the pointer cannot erase a ring that is
27
+ * already there. The two can disagree, and that is the point.
28
+ */
29
+ export declare const isFocusVisible: () => boolean;
30
+ /**
31
+ * Declare how the last interaction reached the page.
32
+ *
33
+ * Ported from React Aria's `setInteractionModality`. A component that moves focus itself —
34
+ * a slider label handing focus to its first thumb — has to say that the move came from the
35
+ * keyboard, or the ring it just earned would not be painted.
36
+ */
37
+ export declare const setInteractionModality: (next: InteractionModality) => void;
38
+ /**
39
+ * The answer that decides whether a ring is painted: moved by a keystroke or a press, never by a
40
+ * bare mouse move. Reactive when read inside a computed.
41
+ */
42
+ export declare const getFocusRingModality: () => InteractionModality;
@@ -0,0 +1,91 @@
1
+ import { shallowRef } from "vue";
2
+ //#region src/composables/interaction-modality.ts
3
+ var modality = shallowRef("keyboard");
4
+ /**
5
+ * The same answer, kept out of the reactive graph.
6
+ *
7
+ * These are two different questions and they need two different answers. "Should a focus ring be
8
+ * painted" may only change when something is pressed or a key is struck — a bare mouse move must
9
+ * not erase a ring that is already there. "How is the user driving the page right now" has to
10
+ * follow the pointer as it moves, because a tooltip decides whether to open on hover by asking it.
11
+ * Answering the second reactively would make every mouse move re-render every focusable thing on
12
+ * the page and drop the ring while doing it, which is why React Aria keeps the same split.
13
+ */
14
+ var latestModality = "keyboard";
15
+ /** Number of live consumers, so the document listeners are attached exactly once. */
16
+ var consumerCount = 0;
17
+ var onGlobalKeydown = (event) => {
18
+ if (event.metaKey || event.altKey || event.ctrlKey) return;
19
+ latestModality = "keyboard";
20
+ modality.value = "keyboard";
21
+ };
22
+ var onGlobalPointerdown = () => {
23
+ latestModality = "pointer";
24
+ modality.value = "pointer";
25
+ };
26
+ /** Tracked without touching the reactive ref, for the reason above. */
27
+ var onGlobalPointerMoved = () => {
28
+ latestModality = "pointer";
29
+ };
30
+ /**
31
+ * Keep the shared modality listeners attached, returning the release.
32
+ *
33
+ * For a component that has to ask how the user is driving the page without otherwise taking part
34
+ * in the interaction lifecycle — a tooltip trigger, which opens on hover only for a real pointer.
35
+ * Without this the answer would be whatever it was when the last interactive component unmounted.
36
+ */
37
+ var retainInteractionModality = () => {
38
+ if (typeof document === "undefined") return () => {};
39
+ if (++consumerCount === 1) {
40
+ document.addEventListener("keydown", onGlobalKeydown, true);
41
+ document.addEventListener("pointerdown", onGlobalPointerdown, true);
42
+ document.addEventListener("pointermove", onGlobalPointerMoved, true);
43
+ document.addEventListener("pointerup", onGlobalPointerMoved, true);
44
+ }
45
+ return () => {
46
+ if (--consumerCount === 0) {
47
+ document.removeEventListener("keydown", onGlobalKeydown, true);
48
+ document.removeEventListener("pointerdown", onGlobalPointerdown, true);
49
+ document.removeEventListener("pointermove", onGlobalPointerMoved, true);
50
+ document.removeEventListener("pointerup", onGlobalPointerMoved, true);
51
+ }
52
+ };
53
+ };
54
+ /**
55
+ * How the user is driving the page right now.
56
+ *
57
+ * Ported from React Aria's `getInteractionModality`. Not reactive on purpose — read it inside an
58
+ * event handler, where the answer is the one that matters.
59
+ */
60
+ var getInteractionModality = () => latestModality;
61
+ /**
62
+ * Whether focus arriving right now is the kind that came from a keyboard.
63
+ *
64
+ * Ported from React Aria's `isFocusVisible`, and reads the same answer React Aria reads — the one
65
+ * that follows the pointer as it moves. Read it inside a handler: a tooltip asks it on focus to
66
+ * tell tabbing to a button apart from clicking it, and only the pointer-following answer knows the
67
+ * user had already reached for the mouse.
68
+ *
69
+ * Not the answer that decides whether a ring is painted. That one lives on `useInteractionStates`
70
+ * and deliberately ignores a bare mouse move, so moving the pointer cannot erase a ring that is
71
+ * already there. The two can disagree, and that is the point.
72
+ */
73
+ var isFocusVisible = () => latestModality === "keyboard";
74
+ /**
75
+ * Declare how the last interaction reached the page.
76
+ *
77
+ * Ported from React Aria's `setInteractionModality`. A component that moves focus itself —
78
+ * a slider label handing focus to its first thumb — has to say that the move came from the
79
+ * keyboard, or the ring it just earned would not be painted.
80
+ */
81
+ var setInteractionModality = (next) => {
82
+ latestModality = next;
83
+ modality.value = next;
84
+ };
85
+ /**
86
+ * The answer that decides whether a ring is painted: moved by a keystroke or a press, never by a
87
+ * bare mouse move. Reactive when read inside a computed.
88
+ */
89
+ var getFocusRingModality = () => modality.value;
90
+ //#endregion
91
+ export { getFocusRingModality, getInteractionModality, isFocusVisible, retainInteractionModality, setInteractionModality };
@@ -1,4 +1,4 @@
1
- import { getInteractionModality } from "./use-interaction-states.js";
1
+ import { getInteractionModality } from "./interaction-modality.js";
2
2
  import { getScrollParent } from "../utils/focus.js";
3
3
  import { usePress } from "./use-press.js";
4
4
  import { useLocalizedStringFormatter } from "./use-localized-string-formatter.js";
@@ -1,5 +1,6 @@
1
1
  import { useControllableState } from "./use-controllable-state.js";
2
- import { DEFAULT_VALIDATION_RESULT, getNativeValidation, useFormValidationState } from "./use-form-validation-state.js";
2
+ import { DEFAULT_VALIDATION_RESULT, getNativeValidation } from "./validation-result.js";
3
+ import { useFormValidationState } from "./use-form-validation-state.js";
3
4
  import { computed, nextTick, toValue } from "vue";
4
5
  //#region src/composables/use-checkbox-group-state.ts
5
6
  /**
@@ -1,6 +1,7 @@
1
1
  import type { CollectionKey, UseCollectionReturn } from "./use-collection.js";
2
2
  import type { FormValidationState, ValidationBehavior, ValidationFunction } from "./use-form-validation-state.js";
3
- import type { FocusStrategy, MenuTriggerState } from "./use-overlay-trigger-state.js";
3
+ import type { MenuTriggerState } from "./use-menu-trigger-state.js";
4
+ import type { FocusStrategy } from "./use-overlay-trigger-state.js";
4
5
  import type { SelectSelectionMode, SelectedItem, SelectedValue } from "./use-select-state.js";
5
6
  import type { DisabledBehavior, UseSelectionManagerReturn } from "./use-selection-manager.js";
6
7
  import type { ComputedRef, MaybeRefOrGetter } from "vue";
@@ -1,8 +1,9 @@
1
1
  import { useControllableState } from "./use-controllable-state.js";
2
- import { useMenuTriggerState } from "./use-overlay-trigger-state.js";
3
2
  import { useCollection } from "./use-collection.js";
4
3
  import { createListCollection } from "../utils/virtualizer-collection.js";
5
- import { isValueMissing, useFormValidationState } from "./use-form-validation-state.js";
4
+ import { isValueMissing } from "./validation-result.js";
5
+ import { useFormValidationState } from "./use-form-validation-state.js";
6
+ import { useMenuTriggerState } from "./use-menu-trigger-state.js";
6
7
  import { useSelectionManager } from "./use-selection-manager.js";
7
8
  import { defaultItemTextValue } from "./use-select-state.js";
8
9
  import { useFilter } from "./use-filter.js";
@@ -1,5 +1,6 @@
1
1
  import { useId as useId$1 } from "./use-id.js";
2
2
  import { ariaHideOutside } from "../utils/aria-hide-outside.js";
3
+ import { usePortal } from "./use-portal.js";
3
4
  import { isAppleDevice } from "../utils/platform.js";
4
5
  import { useOverlayTrigger } from "./use-overlay-trigger.js";
5
6
  import { usePress } from "./use-press.js";
@@ -221,6 +222,7 @@ var useComboBox = (options, state) => {
221
222
  flush: "post",
222
223
  immediate: true
223
224
  });
225
+ const portal = usePortal();
224
226
  /**
225
227
  * Everything but the field and its options is taken out of the accessibility tree while the
226
228
  * popover is open.
@@ -232,10 +234,11 @@ var useComboBox = (options, state) => {
232
234
  watch([
233
235
  () => state.isOpen.value,
234
236
  inputElement,
235
- () => toValue(options.popoverElement)
236
- ], ([isOpen, input, popover], _previous, onCleanup) => {
237
+ () => toValue(options.popoverElement),
238
+ () => portal.root.value
239
+ ], ([isOpen, input, popover, root], _previous, onCleanup) => {
237
240
  if (!isOpen || !popover) return;
238
- onCleanup(ariaHideOutside([input, popover].filter((element) => element != null)));
241
+ onCleanup(ariaHideOutside([input, popover].filter((element) => element != null), { root: root ?? void 0 }));
239
242
  }, {
240
243
  flush: "post",
241
244
  immediate: true
@@ -32,9 +32,5 @@ var useCssVariable = (name, options = {}) => {
32
32
  return value;
33
33
  });
34
34
  };
35
- /** Forget everything read so far. For a test that changes a property between cases. */
36
- var clearCssVariableCache = () => {
37
- cache.clear();
38
- };
39
35
  //#endregion
40
- export { clearCssVariableCache, useCssVariable };
36
+ export { useCssVariable };
@@ -0,0 +1,9 @@
1
+ import type { Locale } from "../utils/locale.js";
2
+ import type { ComputedRef } from "vue";
3
+ /**
4
+ * The locale the browser is set to, following the user changing it.
5
+ *
6
+ * Ported from React Aria's `packages/react-aria/src/i18n/useDefaultLocale.ts` (react-aria 3.51.0),
7
+ * minus its server branch — this package does not render on the server yet.
8
+ */
9
+ export declare const useDefaultLocale: () => ComputedRef<Locale>;
@@ -0,0 +1,39 @@
1
+ import { getDefaultLocale } from "../utils/locale.js";
2
+ import { computed, onScopeDispose, shallowRef } from "vue";
3
+ //#region src/composables/use-default-locale.ts
4
+ /**
5
+ * The browser's locale, shared by every consumer and updated when the user changes it.
6
+ *
7
+ * Module-level rather than per-component: `languagechange` is a window event and the answer is the
8
+ * same for the whole page, so one listener serves all of them. Held behind a reference count so a
9
+ * page with no locale-aware component on it carries no listener.
10
+ */
11
+ var browserLocale = shallowRef(getDefaultLocale());
12
+ var consumerCount = 0;
13
+ var onLanguageChange = () => {
14
+ browserLocale.value = getDefaultLocale();
15
+ };
16
+ /** Attach the shared `languagechange` listener, returning the release for this consumer. */
17
+ var retainBrowserLocale = () => {
18
+ /* c8 ignore next */
19
+ if (typeof window === "undefined") return () => {};
20
+ if (++consumerCount === 1) {
21
+ onLanguageChange();
22
+ window.addEventListener("languagechange", onLanguageChange);
23
+ }
24
+ return () => {
25
+ if (--consumerCount === 0) window.removeEventListener("languagechange", onLanguageChange);
26
+ };
27
+ };
28
+ /**
29
+ * The locale the browser is set to, following the user changing it.
30
+ *
31
+ * Ported from React Aria's `packages/react-aria/src/i18n/useDefaultLocale.ts` (react-aria 3.51.0),
32
+ * minus its server branch — this package does not render on the server yet.
33
+ */
34
+ var useDefaultLocale = () => {
35
+ onScopeDispose(retainBrowserLocale());
36
+ return computed(() => browserLocale.value);
37
+ };
38
+ //#endregion
39
+ export { useDefaultLocale };
@@ -23,6 +23,12 @@ export interface UseDismissableOptions {
23
23
  * should be left alone — the trigger of an already-open submenu, for one.
24
24
  */
25
25
  shouldCloseOnInteractOutside?: (element: Element) => boolean;
26
+ /**
27
+ * The part of the page the overlay belongs to. A press outside it — in another app's window
28
+ * beside this one — is neither an outside interaction nor swallowed, and the overlay is only
29
+ * stacked with overlays whose region overlaps its own. `null` or absent: the whole document.
30
+ */
31
+ region?: MaybeRefOrGetter<Element | null | undefined>;
26
32
  }
27
33
  export interface UseDismissableReturn {
28
34
  /** Bind to the overlay so Escape dismisses it. */
@@ -1,14 +1,8 @@
1
+ import { regionsOverlap } from "../utils/region.js";
1
2
  import { isInTopLayer } from "../utils/top-layer.js";
2
- import { isElementInAnyFocusScope } from "./use-focus-scope.js";
3
+ import { isElementInAnyFocusScope } from "./focus-scope-registry.js";
3
4
  import { computed, onScopeDispose, toValue, watch } from "vue";
4
5
  //#region src/composables/use-dismissable.ts
5
- /**
6
- * Open overlays, innermost last.
7
- *
8
- * Module-level on purpose: a click outside must dismiss exactly one overlay — the innermost —
9
- * and no single overlay can know whether another is nested inside it. React Aria keeps the same
10
- * list for the same reason.
11
- */
12
6
  var visibleOverlays = [];
13
7
  /**
14
8
  * Dismiss an overlay on Escape or on an interaction outside it, ported from React Aria's
@@ -37,11 +31,13 @@ var visibleOverlays = [];
37
31
  * ```
38
32
  */
39
33
  var useDismissable = (options) => {
40
- const layer = {};
34
+ const layer = { region: () => toValue(options.region) ?? null };
41
35
  const isOpen = computed(() => Boolean(toValue(options.isOpen)));
42
36
  const isDismissable = computed(() => Boolean(toValue(options.isDismissable)));
43
37
  const getOverlay = () => toValue(options.overlayRef) ?? null;
44
- const isTopMost = () => visibleOverlays.at(-1) === layer;
38
+ /** The innermost open overlay this one is stacked with — possibly itself. */
39
+ const innermost = () => visibleOverlays.findLast((other) => regionsOverlap(other.region(), layer.region()));
40
+ const isTopMost = () => innermost() === layer;
45
41
  const close = () => {
46
42
  if (isTopMost()) options.onClose?.();
47
43
  };
@@ -61,6 +57,8 @@ var useDismissable = (options) => {
61
57
  const target = event.target;
62
58
  if (!(target instanceof Element) || !target.ownerDocument.documentElement.contains(target)) return false;
63
59
  if (isInTopLayer(target)) return false;
60
+ const region = layer.region();
61
+ if (region && !region.contains(target)) return false;
64
62
  return !event.composedPath().includes(overlay);
65
63
  };
66
64
  /** Whether the pointerdown that began this interaction was outside. */
@@ -72,7 +70,7 @@ var useDismissable = (options) => {
72
70
  if (!isDismissable.value || !isOpen.value || typeof document === "undefined") return;
73
71
  const onPointerdown = (event) => {
74
72
  if (!isOutside(event)) return;
75
- layerAtPointerDown = visibleOverlays.at(-1);
73
+ layerAtPointerDown = innermost();
76
74
  if (options.shouldCloseOnInteractOutside && !options.shouldCloseOnInteractOutside(event.target)) return;
77
75
  pointerDownWasOutside = true;
78
76
  if (isTopMost()) event.stopPropagation();
@@ -1,5 +1,5 @@
1
1
  import { useLocale } from "./use-locale.js";
2
- import { useLocalizedStringDictionary } from "./use-localized-string-formatter.js";
2
+ import { useLocalizedStringDictionary } from "./use-localized-string-dictionary.js";
3
3
  import { datepickerStrings } from "../i18n/datepicker.js";
4
4
  import { computed } from "vue";
5
5
  //#region src/composables/use-display-names.ts
@@ -1,5 +1,5 @@
1
1
  import { isIOS, isWebKit } from "../utils/platform.js";
2
- import { isVirtualClick, isVirtualPointerEvent } from "./use-press.js";
2
+ import { isVirtualClick, isVirtualPointerEvent } from "./virtual-event.js";
3
3
  import { useLocalizedStringFormatter } from "./use-localized-string-formatter.js";
4
4
  import { useDescription } from "./use-description.js";
5
5
  import { dndStrings } from "../i18n/dnd.js";
@@ -1,4 +1,4 @@
1
- import { setInteractionModality } from "./use-interaction-states.js";
1
+ import { setInteractionModality } from "./interaction-modality.js";
2
2
  import { useLocale } from "./use-locale.js";
3
3
  import { DIRECTORY_DRAG_TYPE } from "../utils/dnd-constants.js";
4
4
  import { dragTypesFromSet, getTypes } from "../utils/dnd-data-transfer.js";
@@ -1,16 +1,4 @@
1
1
  import type { MaybeRefOrGetter } from "vue";
2
- /**
3
- * Whether the element sits inside any registered scope.
4
- *
5
- * Any scope rather than only the descendants of one, because an overlay opened from inside
6
- * another is a **sibling** in the DOM rather than a descendant — a submenu renders into its root
7
- * popover's container, and a dropdown opened from a popover makes a container of its own. There
8
- * is no tree to walk here, so a scope boundary is the thing that answers "focus is still in an
9
- * overlay". React Aria walks its scope tree and asks the narrower question; the difference shows
10
- * only when two unrelated overlays are open at once and focus moves between them, where this
11
- * errs towards leaving them open.
12
- */
13
- export declare const isElementInAnyFocusScope: (element: Element) => boolean;
14
2
  export interface UseFocusScopeOptions {
15
3
  /** The element the scope covers. */
16
4
  scopeRef: MaybeRefOrGetter<HTMLElement | null | undefined>;
@@ -35,6 +23,12 @@ export interface UseFocusScopeOptions {
35
23
  * @default false
36
24
  */
37
25
  autoFocus?: MaybeRefOrGetter<boolean | "first" | "last" | undefined>;
26
+ /**
27
+ * The part of the page containment holds focus within. Focus moving to something outside it —
28
+ * another app's window beside this one — is left alone rather than pulled back, and Tab pressed
29
+ * out there is not taken over. `null` or absent: the whole document.
30
+ */
31
+ region?: MaybeRefOrGetter<Element | null | undefined>;
38
32
  }
39
33
  /**
40
34
  * Keep focus inside an overlay and give it back when the overlay closes, ported from React