@nizivo/react 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +120 -2
- package/THIRD_PARTY_NOTICES +61 -0
- package/dist/_engines/react-aria/dialog.client.js +134 -0
- package/dist/_engines/react-aria/dom-allowlist.js +19 -0
- package/dist/_engines/react-aria/dom.js +31 -0
- package/dist/_engines/react-aria/locale.client.js +13 -0
- package/dist/_engines/react-aria/menu.client.js +225 -0
- package/dist/_engines/react-aria/overlay.client.js +41 -0
- package/dist/_engines/react-aria/popup.client.js +127 -0
- package/dist/_engines/react-aria/select.client.js +170 -0
- package/dist/_engines/react-aria/tabs.client.js +95 -0
- package/dist/_engines/react-aria/tooltip.client.js +72 -0
- package/dist/_internal/cx.js +11 -0
- package/dist/_internal/engine-part.d.ts +1 -0
- package/dist/_internal/field.js +20 -0
- package/dist/_internal/glyphs.js +34 -0
- package/dist/_internal/keys.js +62 -0
- package/dist/_internal/overlay.d.ts +9 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/alert/alert-dismiss.client.d.ts +32 -0
- package/dist/alert/alert-dismiss.client.js +36 -0
- package/dist/alert/alert.d.ts +28 -0
- package/dist/alert/alert.js +44 -0
- package/dist/alert/index.d.ts +3 -0
- package/dist/alert/index.js +3 -0
- package/dist/augment.d.ts +13 -0
- package/dist/augment.js +0 -0
- package/dist/badge/badge.d.ts +19 -0
- package/dist/badge/badge.js +22 -0
- package/dist/badge/index.d.ts +2 -0
- package/dist/badge/index.js +1 -0
- package/dist/button/button.d.ts +54 -0
- package/dist/button/button.js +62 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.js +1 -0
- package/dist/card/card.d.ts +57 -0
- package/dist/card/card.js +62 -0
- package/dist/card/card.parts.d.ts +6 -0
- package/dist/card/card.parts.js +14 -0
- package/dist/card/index.d.ts +3 -0
- package/dist/card/index.js +3 -0
- package/dist/checkbox/checkbox.client.d.ts +37 -0
- package/dist/checkbox/checkbox.client.js +81 -0
- package/dist/checkbox/index.d.ts +2 -0
- package/dist/checkbox/index.js +1 -0
- package/dist/config/config.client.d.ts +9 -0
- package/dist/config/config.client.js +18 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +1 -0
- package/dist/container/container.d.ts +13 -0
- package/dist/container/container.js +16 -0
- package/dist/container/index.d.ts +2 -0
- package/dist/container/index.js +1 -0
- package/dist/contracts/details.d.ts +13 -0
- package/dist/contracts/details.js +9 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/merge-props.d.ts +16 -0
- package/dist/contracts/merge-props.js +76 -0
- package/dist/contracts/render.d.ts +17 -0
- package/dist/contracts/render.js +43 -0
- package/dist/contracts/state.d.ts +42 -0
- package/dist/contracts/state.js +42 -0
- package/dist/css/alert.css +2 -0
- package/dist/css/badge.css +2 -0
- package/dist/css/base.css +2 -0
- package/dist/css/button.css +2 -0
- package/dist/css/card.css +2 -0
- package/dist/css/checkbox.css +2 -0
- package/dist/css/container.css +2 -0
- package/dist/css/delta.css +2 -0
- package/dist/css/dialog.css +2 -0
- package/dist/css/empty-state.css +2 -0
- package/dist/css/field.css +2 -0
- package/dist/css/fieldset.css +2 -0
- package/dist/css/grid.css +2 -0
- package/dist/css/heading.css +2 -0
- package/dist/css/icon-button.css +2 -0
- package/dist/css/image.css +2 -0
- package/dist/css/inline-error.css +2 -0
- package/dist/css/inline.css +2 -0
- package/dist/css/input.css +2 -0
- package/dist/css/kbd.css +2 -0
- package/dist/css/menu.css +2 -0
- package/dist/css/meter.css +2 -0
- package/dist/css/native-select.css +2 -0
- package/dist/css/progress-bar.css +2 -0
- package/dist/css/progress-ring.css +2 -0
- package/dist/css/radio-group.css +2 -0
- package/dist/css/segmented-control.css +2 -0
- package/dist/css/select.css +2 -0
- package/dist/css/separator.css +2 -0
- package/dist/css/skeleton.css +2 -0
- package/dist/css/stack.css +2 -0
- package/dist/css/switch.css +2 -0
- package/dist/css/tab-nav.css +2 -0
- package/dist/css/tabs.css +2 -0
- package/dist/css/text.css +2 -0
- package/dist/css/textarea.css +2 -0
- package/dist/css/tooltip.css +2 -0
- package/dist/delta/delta.d.ts +32 -0
- package/dist/delta/delta.js +50 -0
- package/dist/delta/index.d.ts +2 -0
- package/dist/delta/index.js +1 -0
- package/dist/dialog/dialog.client.d.ts +25 -0
- package/dist/dialog/dialog.client.js +167 -0
- package/dist/dialog/dialog.contract.d.ts +97 -0
- package/dist/dialog/dialog.parts.d.ts +6 -0
- package/dist/dialog/dialog.parts.js +15 -0
- package/dist/dialog/index.d.ts +5 -0
- package/dist/dialog/index.js +3 -0
- package/dist/empty-state/empty-state.d.ts +18 -0
- package/dist/empty-state/empty-state.js +32 -0
- package/dist/empty-state/index.d.ts +2 -0
- package/dist/empty-state/index.js +1 -0
- package/dist/field/field-scope.client.d.ts +50 -0
- package/dist/field/field-scope.client.js +205 -0
- package/dist/field/field-static.d.ts +122 -0
- package/dist/field/field-static.js +159 -0
- package/dist/field/field.d.ts +5 -0
- package/dist/field/field.js +53 -0
- package/dist/field/index.d.ts +4 -0
- package/dist/field/index.js +4 -0
- package/dist/fieldset/fieldset.d.ts +36 -0
- package/dist/fieldset/fieldset.js +63 -0
- package/dist/fieldset/index.d.ts +2 -0
- package/dist/fieldset/index.js +1 -0
- package/dist/grid/grid.d.ts +38 -0
- package/dist/grid/grid.js +28 -0
- package/dist/grid/index.d.ts +2 -0
- package/dist/grid/index.js +1 -0
- package/dist/heading/heading.d.ts +27 -0
- package/dist/heading/heading.js +26 -0
- package/dist/heading/index.d.ts +2 -0
- package/dist/heading/index.js +1 -0
- package/dist/icon-button/icon-button-static.d.ts +23 -0
- package/dist/icon-button/icon-button-static.js +83 -0
- package/dist/icon-button/icon-button.client.js +29 -0
- package/dist/icon-button/icon-button.contract.d.ts +89 -0
- package/dist/icon-button/icon-button.d.ts +5 -0
- package/dist/icon-button/icon-button.js +21 -0
- package/dist/icon-button/index.d.ts +4 -0
- package/dist/icon-button/index.js +3 -0
- package/dist/image/image.d.ts +28 -0
- package/dist/image/image.js +24 -0
- package/dist/image/index.d.ts +2 -0
- package/dist/image/index.js +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +51 -0
- package/dist/inline/index.d.ts +2 -0
- package/dist/inline/index.js +1 -0
- package/dist/inline/inline.d.ts +16 -0
- package/dist/inline/inline.js +14 -0
- package/dist/inline-error/index.d.ts +2 -0
- package/dist/inline-error/index.js +1 -0
- package/dist/inline-error/inline-error.d.ts +17 -0
- package/dist/inline-error/inline-error.js +33 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +1 -0
- package/dist/input/input.client.d.ts +39 -0
- package/dist/input/input.client.js +82 -0
- package/dist/kbd/index.d.ts +2 -0
- package/dist/kbd/index.js +1 -0
- package/dist/kbd/kbd.d.ts +19 -0
- package/dist/kbd/kbd.js +68 -0
- package/dist/menu/index.d.ts +5 -0
- package/dist/menu/index.js +3 -0
- package/dist/menu/menu.client.d.ts +14 -0
- package/dist/menu/menu.client.js +152 -0
- package/dist/menu/menu.contract.d.ts +152 -0
- package/dist/menu/menu.parts.d.ts +6 -0
- package/dist/menu/menu.parts.js +17 -0
- package/dist/meter/index.d.ts +2 -0
- package/dist/meter/index.js +1 -0
- package/dist/meter/meter.d.ts +43 -0
- package/dist/meter/meter.js +59 -0
- package/dist/native-select/index.d.ts +2 -0
- package/dist/native-select/index.js +1 -0
- package/dist/native-select/native-select.d.ts +43 -0
- package/dist/native-select/native-select.js +56 -0
- package/dist/progress-bar/index.d.ts +2 -0
- package/dist/progress-bar/index.js +1 -0
- package/dist/progress-bar/progress-bar.d.ts +34 -0
- package/dist/progress-bar/progress-bar.js +55 -0
- package/dist/progress-ring/index.d.ts +2 -0
- package/dist/progress-ring/index.js +1 -0
- package/dist/progress-ring/progress-ring.d.ts +38 -0
- package/dist/progress-ring/progress-ring.js +67 -0
- package/dist/radio-group/index.d.ts +3 -0
- package/dist/radio-group/index.js +3 -0
- package/dist/radio-group/radio-group.client.d.ts +22 -0
- package/dist/radio-group/radio-group.client.js +70 -0
- package/dist/radio-group/radio-group.d.ts +28 -0
- package/dist/radio-group/radio-group.js +35 -0
- package/dist/segmented-control/index.d.ts +3 -0
- package/dist/segmented-control/index.js +3 -0
- package/dist/segmented-control/segmented-control.client.d.ts +61 -0
- package/dist/segmented-control/segmented-control.client.js +156 -0
- package/dist/segmented-control/segmented-control.parts.d.ts +6 -0
- package/dist/segmented-control/segmented-control.parts.js +9 -0
- package/dist/select/index.d.ts +5 -0
- package/dist/select/index.js +3 -0
- package/dist/select/select.client.d.ts +12 -0
- package/dist/select/select.client.js +152 -0
- package/dist/select/select.contract.d.ts +119 -0
- package/dist/select/select.parts.d.ts +6 -0
- package/dist/select/select.parts.js +15 -0
- package/dist/separator/index.d.ts +2 -0
- package/dist/separator/index.js +1 -0
- package/dist/separator/separator.d.ts +26 -0
- package/dist/separator/separator.js +23 -0
- package/dist/skeleton/index.d.ts +2 -0
- package/dist/skeleton/index.js +1 -0
- package/dist/skeleton/skeleton.d.ts +20 -0
- package/dist/skeleton/skeleton.js +19 -0
- package/dist/stack/index.d.ts +2 -0
- package/dist/stack/index.js +1 -0
- package/dist/stack/stack.d.ts +17 -0
- package/dist/stack/stack.js +14 -0
- package/dist/styles.css +536 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +1 -0
- package/dist/switch/switch.client.d.ts +33 -0
- package/dist/switch/switch.client.js +63 -0
- package/dist/tab-nav/index.d.ts +3 -0
- package/dist/tab-nav/index.js +3 -0
- package/dist/tab-nav/tab-nav.d.ts +36 -0
- package/dist/tab-nav/tab-nav.js +47 -0
- package/dist/tab-nav/tab-nav.parts.d.ts +6 -0
- package/dist/tab-nav/tab-nav.parts.js +9 -0
- package/dist/tabs/index.d.ts +4 -0
- package/dist/tabs/index.js +3 -0
- package/dist/tabs/tabs.client.d.ts +8 -0
- package/dist/tabs/tabs.client.js +68 -0
- package/dist/tabs/tabs.contract.d.ts +89 -0
- package/dist/tabs/tabs.parts.d.ts +6 -0
- package/dist/tabs/tabs.parts.js +11 -0
- package/dist/text/index.d.ts +2 -0
- package/dist/text/index.js +1 -0
- package/dist/text/text.d.ts +21 -0
- package/dist/text/text.js +17 -0
- package/dist/textarea/index.d.ts +2 -0
- package/dist/textarea/index.js +1 -0
- package/dist/textarea/textarea.client.d.ts +18 -0
- package/dist/textarea/textarea.client.js +31 -0
- package/dist/theme-script/index.d.ts +2 -0
- package/dist/theme-script/index.js +1 -0
- package/dist/theme-script/theme-script.d.ts +43 -0
- package/dist/theme-script/theme-script.js +46 -0
- package/dist/tooltip/index.d.ts +5 -0
- package/dist/tooltip/index.js +3 -0
- package/dist/tooltip/tooltip.client.d.ts +12 -0
- package/dist/tooltip/tooltip.client.js +51 -0
- package/dist/tooltip/tooltip.contract.d.ts +60 -0
- package/dist/tooltip/tooltip.parts.d.ts +6 -0
- package/dist/tooltip/tooltip.parts.js +10 -0
- package/package.json +284 -3
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { mergeProps } from "../../contracts/merge-props.js";
|
|
3
|
+
import { details } from "../../contracts/details.js";
|
|
4
|
+
import { stateAttrs } from "../../contracts/state.js";
|
|
5
|
+
import { defined, engineDom } from "./dom.js";
|
|
6
|
+
import { EngineLocale } from "./locale.client.js";
|
|
7
|
+
import { popupStyle, resolveSide, toPlacement } from "./overlay.client.js";
|
|
8
|
+
import { ReturnFocus, useTabOut, useTriggerRef } from "./popup.client.js";
|
|
9
|
+
import { Children, createContext, createElement, isValidElement, use, useRef, useState } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Button, Header, Menu, MenuItem, MenuSection, MenuTrigger, Popover, RootMenuTriggerStateContext, Separator, useLocale } from "react-aria-components";
|
|
12
|
+
//#region src/_engines/react-aria/menu.client.tsx
|
|
13
|
+
/**
|
|
14
|
+
* Uncontrolled checkbox and radio state, kept by the Root: the popup and everything in it unmount on
|
|
15
|
+
* close, so state held there would reset on every open. A checkbox item is identified by its text, a
|
|
16
|
+
* radio group by its items' values; without either, the state lasts while the popup is open.
|
|
17
|
+
*/
|
|
18
|
+
const Kept = createContext(null);
|
|
19
|
+
function useKept(key, initial, read) {
|
|
20
|
+
const store = use(Kept);
|
|
21
|
+
const [value, setValue] = useState(() => {
|
|
22
|
+
const kept = key === void 0 ? void 0 : store?.get(key);
|
|
23
|
+
return read(kept) ? kept : initial;
|
|
24
|
+
});
|
|
25
|
+
return [value, (next) => {
|
|
26
|
+
if (key !== void 0) store?.set(key, next);
|
|
27
|
+
setValue(next);
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
const isBoolean = (value) => typeof value === "boolean";
|
|
31
|
+
const isChoice = (value) => typeof value === "string" || value === null;
|
|
32
|
+
/** The `value` props of the radio items under `children`, through fragments and wrappers. */
|
|
33
|
+
function itemValues(children, out = []) {
|
|
34
|
+
Children.forEach(children, (child) => {
|
|
35
|
+
if (!isValidElement(child)) return;
|
|
36
|
+
if (typeof child.props.value === "string") out.push(child.props.value);
|
|
37
|
+
else itemValues(child.props.children, out);
|
|
38
|
+
});
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
const Root = ({ open, defaultOpen, onOpenChange, children }) => {
|
|
42
|
+
const [store] = useState(() => /* @__PURE__ */ new Map());
|
|
43
|
+
return /* @__PURE__ */ jsx(EngineLocale, { children: /* @__PURE__ */ jsx(Kept, {
|
|
44
|
+
value: store,
|
|
45
|
+
children: /* @__PURE__ */ jsx(MenuTrigger, {
|
|
46
|
+
...defined({
|
|
47
|
+
isOpen: open,
|
|
48
|
+
defaultOpen
|
|
49
|
+
}),
|
|
50
|
+
onOpenChange: (isOpen) => onOpenChange?.(isOpen, details("unknown")),
|
|
51
|
+
children
|
|
52
|
+
})
|
|
53
|
+
}) });
|
|
54
|
+
};
|
|
55
|
+
const Trigger = ({ render, children, ...dom }) => {
|
|
56
|
+
const open = use(RootMenuTriggerStateContext)?.isOpen ?? false;
|
|
57
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
58
|
+
render: (p, rp) => {
|
|
59
|
+
const s = {
|
|
60
|
+
open,
|
|
61
|
+
pressed: rp.isPressed && !open,
|
|
62
|
+
focusVisible: rp.isFocusVisible,
|
|
63
|
+
disabled: rp.isDisabled
|
|
64
|
+
};
|
|
65
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
66
|
+
},
|
|
67
|
+
children
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The engine names the menu by its trigger (`aria-labelledby`), which would outrank an `aria-label`
|
|
72
|
+
* the leaf passes; with one, the label names the menu instead.
|
|
73
|
+
*/
|
|
74
|
+
function listDom(engine, list) {
|
|
75
|
+
const out = engineDom(engine);
|
|
76
|
+
if (list["aria-label"] !== void 0) delete out["aria-labelledby"];
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
const Popup = ({ side, align, sideOffset, container, list, render, children, ...dom }) => {
|
|
80
|
+
const { direction } = useLocale();
|
|
81
|
+
const popup = useRef(null);
|
|
82
|
+
const tabOut = useTabOut(popup);
|
|
83
|
+
const trigger = useTriggerRef();
|
|
84
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
85
|
+
placement: toPlacement(side, align),
|
|
86
|
+
offset: sideOffset,
|
|
87
|
+
...defined({ UNSTABLE_portalContainer: container ?? void 0 }),
|
|
88
|
+
render: (p, rp) => {
|
|
89
|
+
const s = {
|
|
90
|
+
entering: rp.isEntering,
|
|
91
|
+
exiting: rp.isExiting,
|
|
92
|
+
side: resolveSide(rp.placement, side, direction)
|
|
93
|
+
};
|
|
94
|
+
const engine = {
|
|
95
|
+
...engineDom(p),
|
|
96
|
+
style: popupStyle(p.style)
|
|
97
|
+
};
|
|
98
|
+
const attrs = stateAttrs({
|
|
99
|
+
open: !rp.isExiting,
|
|
100
|
+
...s,
|
|
101
|
+
align
|
|
102
|
+
});
|
|
103
|
+
return render(mergeProps(engine, dom, attrs, { ref: popup }), s);
|
|
104
|
+
},
|
|
105
|
+
children: [/* @__PURE__ */ jsx(ReturnFocus, {
|
|
106
|
+
trigger,
|
|
107
|
+
popup
|
|
108
|
+
}), /* @__PURE__ */ jsx(Menu, {
|
|
109
|
+
render: (p) => createElement("div", mergeProps(listDom(p, list), list, { onKeyDown: tabOut })),
|
|
110
|
+
children
|
|
111
|
+
})]
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const Item = ({ textValue, disabled, onAction, closeOnSelect, href, target, rel, download, hrefLang, ping, referrerPolicy, render, children, ...dom }) => /* @__PURE__ */ jsx(MenuItem, {
|
|
115
|
+
...defined({
|
|
116
|
+
textValue,
|
|
117
|
+
isDisabled: disabled,
|
|
118
|
+
onAction,
|
|
119
|
+
shouldCloseOnSelect: closeOnSelect,
|
|
120
|
+
href,
|
|
121
|
+
target,
|
|
122
|
+
rel,
|
|
123
|
+
download,
|
|
124
|
+
hrefLang,
|
|
125
|
+
ping,
|
|
126
|
+
referrerPolicy
|
|
127
|
+
}),
|
|
128
|
+
render: (p, rp) => {
|
|
129
|
+
const s = {
|
|
130
|
+
highlighted: rp.isFocused,
|
|
131
|
+
disabled: rp.isDisabled,
|
|
132
|
+
focusVisible: rp.isFocusVisible,
|
|
133
|
+
checked: false
|
|
134
|
+
};
|
|
135
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
136
|
+
},
|
|
137
|
+
children
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* React Aria derives checkbox semantics from a selection scope (a Menu or MenuSection with
|
|
141
|
+
* `selectionMode`), which would turn every item in that scope into a checkbox. A checkbox item owns
|
|
142
|
+
* its own state instead: the adapter keeps it, toggles it on the item's action and writes the
|
|
143
|
+
* `menuitemcheckbox` role and `aria-checked` the engine would have written.
|
|
144
|
+
*/
|
|
145
|
+
const CheckboxItem = ({ checked, defaultChecked, onCheckedChange, textValue, disabled, onAction, closeOnSelect, render, children, ...dom }) => {
|
|
146
|
+
const [own, setOwn] = useKept(textValue === void 0 ? void 0 : `checkbox:${textValue}`, defaultChecked ?? false, isBoolean);
|
|
147
|
+
const isChecked = checked ?? own;
|
|
148
|
+
return /* @__PURE__ */ jsx(MenuItem, {
|
|
149
|
+
...defined({
|
|
150
|
+
textValue,
|
|
151
|
+
isDisabled: disabled
|
|
152
|
+
}),
|
|
153
|
+
shouldCloseOnSelect: closeOnSelect ?? false,
|
|
154
|
+
onAction: () => {
|
|
155
|
+
const next = !isChecked;
|
|
156
|
+
if (checked === void 0) setOwn(next);
|
|
157
|
+
onCheckedChange?.(next, details("item-press"));
|
|
158
|
+
onAction?.();
|
|
159
|
+
},
|
|
160
|
+
render: (p, rp) => {
|
|
161
|
+
const s = {
|
|
162
|
+
highlighted: rp.isFocused,
|
|
163
|
+
disabled: rp.isDisabled,
|
|
164
|
+
focusVisible: rp.isFocusVisible,
|
|
165
|
+
checked: isChecked
|
|
166
|
+
};
|
|
167
|
+
const aria = {
|
|
168
|
+
role: "menuitemcheckbox",
|
|
169
|
+
"aria-checked": isChecked
|
|
170
|
+
};
|
|
171
|
+
return render(mergeProps(engineDom(p), aria, dom, stateAttrs(s)), s);
|
|
172
|
+
},
|
|
173
|
+
children
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
/** A single-selection scope: React Aria gives its items `menuitemradio` and `aria-checked`. */
|
|
177
|
+
const RadioGroup = ({ value, defaultValue, onValueChange, render, children, ...dom }) => {
|
|
178
|
+
const values = itemValues(children);
|
|
179
|
+
const [own, setOwn] = useKept(values.length === 0 ? void 0 : `radio:${values.join("\0")}`, defaultValue ?? null, isChoice);
|
|
180
|
+
const current = value === void 0 ? own : value;
|
|
181
|
+
return /* @__PURE__ */ jsx(MenuSection, {
|
|
182
|
+
selectionMode: "single",
|
|
183
|
+
disallowEmptySelection: true,
|
|
184
|
+
selectedKeys: current === null ? [] : [current],
|
|
185
|
+
onSelectionChange: (selection) => {
|
|
186
|
+
if (selection === "all") return;
|
|
187
|
+
const [key] = selection;
|
|
188
|
+
if (key === void 0) return;
|
|
189
|
+
if (value === void 0) setOwn(String(key));
|
|
190
|
+
onValueChange?.(String(key), details("unknown"));
|
|
191
|
+
},
|
|
192
|
+
render: (p) => render(mergeProps(engineDom(p), dom), {}),
|
|
193
|
+
children
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const RadioItem = ({ value, textValue, disabled, onAction, closeOnSelect, render, children, ...dom }) => /* @__PURE__ */ jsx(MenuItem, {
|
|
197
|
+
id: value,
|
|
198
|
+
...defined({
|
|
199
|
+
textValue,
|
|
200
|
+
isDisabled: disabled,
|
|
201
|
+
onAction,
|
|
202
|
+
shouldCloseOnSelect: closeOnSelect
|
|
203
|
+
}),
|
|
204
|
+
render: (p, rp) => {
|
|
205
|
+
const s = {
|
|
206
|
+
highlighted: rp.isFocused,
|
|
207
|
+
disabled: rp.isDisabled,
|
|
208
|
+
focusVisible: rp.isFocusVisible,
|
|
209
|
+
checked: rp.isSelected
|
|
210
|
+
};
|
|
211
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
212
|
+
},
|
|
213
|
+
children
|
|
214
|
+
});
|
|
215
|
+
const Group = ({ render, children, ...dom }) => /* @__PURE__ */ jsx(MenuSection, {
|
|
216
|
+
render: (p) => render(mergeProps(engineDom(p), dom), {}),
|
|
217
|
+
children
|
|
218
|
+
});
|
|
219
|
+
const GroupLabel = ({ render, children, ...dom }) => /* @__PURE__ */ jsx(Header, {
|
|
220
|
+
render: (p) => render(mergeProps(engineDom(p), dom), {}),
|
|
221
|
+
children
|
|
222
|
+
});
|
|
223
|
+
const Separator$1 = ({ render, ...dom }) => /* @__PURE__ */ jsx(Separator, { render: (p) => render(mergeProps(engineDom(p), dom), {}) });
|
|
224
|
+
//#endregion
|
|
225
|
+
export { CheckboxItem, Group, GroupLabel, Item, Popup, RadioGroup, RadioItem, Root, Separator$1 as Separator, Trigger };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { UNSAFE_PortalProvider } from "react-aria/PortalProvider";
|
|
4
|
+
//#region src/_engines/react-aria/overlay.client.tsx
|
|
5
|
+
/** Nested overlays rendered inside `children` portal into `container` (§12.1 L2). */
|
|
6
|
+
function OverlayScope({ container, children }) {
|
|
7
|
+
return /* @__PURE__ */ jsx(UNSAFE_PortalProvider, {
|
|
8
|
+
getContainer: () => container.current ?? (typeof document === "undefined" ? null : document.body),
|
|
9
|
+
children
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** React Aria's inert exemption, as props for an element the adapter renders itself. */
|
|
13
|
+
const TOP_LAYER = { "data-react-aria-top-layer": "" };
|
|
14
|
+
/** Logical side + alignment → a React Aria placement. */
|
|
15
|
+
function toPlacement(side, align) {
|
|
16
|
+
if (align === "center") return side;
|
|
17
|
+
if (side === "top" || side === "bottom") return `${side} ${align}`;
|
|
18
|
+
return `${side} ${align === "start" ? "top" : "bottom"}`;
|
|
19
|
+
}
|
|
20
|
+
/** The resolved physical side: the engine's placement after flip and RTL, else the requested side. */
|
|
21
|
+
function resolveSide(placement, requested, direction) {
|
|
22
|
+
if (placement === "top" || placement === "bottom" || placement === "left" || placement === "right") return placement;
|
|
23
|
+
if (requested === "start") return direction === "rtl" ? "right" : "left";
|
|
24
|
+
if (requested === "end") return direction === "rtl" ? "left" : "right";
|
|
25
|
+
return requested;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The engine's positioning style without its fixed `z-index` (popups sit in the one
|
|
29
|
+
* `--nz-z-overlay` band, §12.1), plus the runtime variables Nizivo documents (§8.3).
|
|
30
|
+
*/
|
|
31
|
+
function popupStyle(style) {
|
|
32
|
+
if (typeof style !== "object" || style === null) return void 0;
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [key, value] of Object.entries(style)) if (key !== "zIndex") out[key] = value;
|
|
35
|
+
if (out["--trigger-width"] != null) out["--nz-trigger-width"] = out["--trigger-width"];
|
|
36
|
+
if (out["--trigger-anchor-point"] != null) out["--nz-transform-origin"] = out["--trigger-anchor-point"];
|
|
37
|
+
if (typeof out["maxHeight"] === "number") out["--nz-available-height"] = `${out["maxHeight"]}px`;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { OverlayScope, TOP_LAYER, popupStyle, resolveSide, toPlacement };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { use, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { OverlayTriggerStateContext, PopoverContext } from "react-aria-components";
|
|
4
|
+
//#region src/_engines/react-aria/popup.client.tsx
|
|
5
|
+
const TABBABLE = "a[href], area[href], button, input, select, textarea, iframe, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])";
|
|
6
|
+
/**
|
|
7
|
+
* The tabbable element after (or before) `from` in its dialog, else in the document, skipping `skip`
|
|
8
|
+
* (the popup itself). `inert` is not checked: the engine marks the page inert while the popup is
|
|
9
|
+
* open, and lifts it as the popup closes.
|
|
10
|
+
*/
|
|
11
|
+
function adjacentTabbable(from, backwards, skip) {
|
|
12
|
+
const root = from.closest("[role=\"dialog\"], [role=\"alertdialog\"], dialog") ?? from.ownerDocument;
|
|
13
|
+
const order = backwards ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;
|
|
14
|
+
const candidates = [...root.querySelectorAll(TABBABLE)].filter((element) => element.tabIndex >= 0 && !element.matches(":disabled") && (skip === null || !skip.contains(element)) && !from.contains(element) && (from.compareDocumentPosition(element) & order) !== 0 && element.getClientRects().length > 0);
|
|
15
|
+
return backwards ? candidates.at(-1) : candidates[0];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Tab and Shift+Tab out of a menu or listbox popup (§12.2 Dismiss stack; APG menu button): the popup
|
|
19
|
+
* closes and focus moves to the element after (or before) its trigger. React Aria 1.22.1 keeps Tab
|
|
20
|
+
* inside its modal popovers, so the adapter handles the key on the popup's list element.
|
|
21
|
+
*/
|
|
22
|
+
function useTabOut(popup) {
|
|
23
|
+
const state = use(OverlayTriggerStateContext);
|
|
24
|
+
const context = use(PopoverContext);
|
|
25
|
+
const trigger = typeof context === "object" && context !== null && "triggerRef" in context ? context.triggerRef : void 0;
|
|
26
|
+
return (event) => {
|
|
27
|
+
if (event.key !== "Tab" || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
28
|
+
const from = typeof trigger === "object" && trigger !== null && "current" in trigger ? trigger.current : void 0;
|
|
29
|
+
if (state === null || !(from instanceof HTMLElement)) return;
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
const target = adjacentTabbable(from, event.shiftKey, popup.current) ?? from;
|
|
33
|
+
state.close();
|
|
34
|
+
requestAnimationFrame(() => target.focus());
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function isElementRef(value) {
|
|
38
|
+
return typeof value === "object" && value !== null && "current" in value && (value.current === null || value.current instanceof HTMLElement);
|
|
39
|
+
}
|
|
40
|
+
/** The enclosing MenuTrigger's, Select's or DialogTrigger's trigger, from the engine's PopoverContext. */
|
|
41
|
+
function useTriggerRef() {
|
|
42
|
+
const context = use(PopoverContext);
|
|
43
|
+
const ref = typeof context === "object" && context !== null && "triggerRef" in context ? context.triggerRef : void 0;
|
|
44
|
+
return isElementRef(ref) ? ref : void 0;
|
|
45
|
+
}
|
|
46
|
+
/** The family's open popups and their triggers: the parent-overlay step below. */
|
|
47
|
+
const OPEN = /* @__PURE__ */ new Map();
|
|
48
|
+
/**
|
|
49
|
+
* React Aria's FocusScope dispatches this cancelable event on the element it is about to focus when a
|
|
50
|
+
* scope unmounts with focus lost to <body> (react-aria 3.53.1, FocusScope `restoreFocusToElement`); a
|
|
51
|
+
* prevented event leaves focus alone. It is how `returnFocus` overrides the engine's choice.
|
|
52
|
+
*/
|
|
53
|
+
const ENGINE_RESTORE = "react-aria-focus-scope-restore";
|
|
54
|
+
/** A focus target: on the page, and not <body> (where focus is when nothing has it). */
|
|
55
|
+
const usable = (element) => element instanceof HTMLElement && element.isConnected && element !== element.ownerDocument.body;
|
|
56
|
+
/** Whether the popup is still in the DOM: Strict Mode's mount-time replay of the effect. */
|
|
57
|
+
const mounted = (popup) => popup.current?.isConnected === true;
|
|
58
|
+
/** The element a `returnFocus` ref or function names, if any. */
|
|
59
|
+
const named = (target) => typeof target === "function" ? target() : typeof target === "object" ? target.current : null;
|
|
60
|
+
/**
|
|
61
|
+
* Focus return (§12.2): `returnFocus` (`true`, an element ref, a function returning one, or `false`)
|
|
62
|
+
* → the original trigger if still connected → the element that had focus as the popup opened → the
|
|
63
|
+
* parent overlay's trigger (a Dialog opened from a Menu item returns to the Menu button) → <body>,
|
|
64
|
+
* with a development warning. Rendered inside every modal popup of the family (Dialog, Menu,
|
|
65
|
+
* Select).
|
|
66
|
+
*
|
|
67
|
+
* React Aria returns focus to the element that had it when the popup opened, which covers the
|
|
68
|
+
* common case; this completes the chain where it has nowhere to go: a popup open on the server
|
|
69
|
+
* (`defaultOpen`) or opened from code with nothing focused, and a Dialog opened from an item of a
|
|
70
|
+
* Menu that had no focus origin of its own loses its opener with the Menu. Each popup registers
|
|
71
|
+
* itself while open, so one opened from inside another records that one's trigger as its parent
|
|
72
|
+
* step. It never moves focus that went somewhere (an outside click on a control, Tab out of a
|
|
73
|
+
* menu).
|
|
74
|
+
*/
|
|
75
|
+
function ReturnFocus({ trigger, popup, target = true }) {
|
|
76
|
+
const [origin] = useState(() => {
|
|
77
|
+
const opener = globalThis.document?.activeElement;
|
|
78
|
+
let parent;
|
|
79
|
+
for (const [open, openTrigger] of OPEN) if (opener != null && open.current?.contains(opener)) parent = openTrigger?.current;
|
|
80
|
+
return [opener, parent];
|
|
81
|
+
});
|
|
82
|
+
const latest = useRef(target);
|
|
83
|
+
useLayoutEffect(() => {
|
|
84
|
+
latest.current = target;
|
|
85
|
+
});
|
|
86
|
+
const shown = useRef(false);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (mounted(popup)) shown.current = true;
|
|
89
|
+
});
|
|
90
|
+
useLayoutEffect(() => {
|
|
91
|
+
const element = trigger?.current;
|
|
92
|
+
OPEN.set(popup, trigger);
|
|
93
|
+
return () => {
|
|
94
|
+
OPEN.delete(popup);
|
|
95
|
+
const doc = document;
|
|
96
|
+
let handled = false;
|
|
97
|
+
const onRestore = (event) => {
|
|
98
|
+
const own = latest.current;
|
|
99
|
+
const to = named(own);
|
|
100
|
+
if (event.defaultPrevented || mounted(popup) || own !== false && !usable(to)) return;
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
handled = true;
|
|
103
|
+
if (usable(to)) to.focus();
|
|
104
|
+
};
|
|
105
|
+
doc.addEventListener(ENGINE_RESTORE, onRestore, true);
|
|
106
|
+
requestAnimationFrame(() => {
|
|
107
|
+
doc.removeEventListener(ENGINE_RESTORE, onRestore, true);
|
|
108
|
+
const own = latest.current;
|
|
109
|
+
if (handled || !shown.current || own === false || mounted(popup) || (doc.activeElement ?? doc.body) !== doc.body) return;
|
|
110
|
+
const next = [
|
|
111
|
+
named(own),
|
|
112
|
+
element,
|
|
113
|
+
...origin
|
|
114
|
+
].find(usable);
|
|
115
|
+
if (next) next.focus();
|
|
116
|
+
else if (process.env.NODE_ENV !== "production") console.warn("Nizivo: an overlay closed and focus had nowhere to return (its trigger, the element that opened it and its parent overlay are gone), so it is on <body>. Pass `returnFocus` an element that stays on the page.");
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
}, [
|
|
120
|
+
trigger,
|
|
121
|
+
popup,
|
|
122
|
+
origin
|
|
123
|
+
]);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { ReturnFocus, useTabOut, useTriggerRef };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { mergeProps } from "../../contracts/merge-props.js";
|
|
3
|
+
import { details } from "../../contracts/details.js";
|
|
4
|
+
import { stateAttrs } from "../../contracts/state.js";
|
|
5
|
+
import { defined, engineDom } from "./dom.js";
|
|
6
|
+
import { EngineLocale } from "./locale.client.js";
|
|
7
|
+
import { popupStyle, resolveSide, toPlacement } from "./overlay.client.js";
|
|
8
|
+
import { ReturnFocus, useTabOut, useTriggerRef } from "./popup.client.js";
|
|
9
|
+
import { createElement, use, useEffect, useLayoutEffect, useRef } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Button, Header, ListBox, ListBoxItem, ListBoxSection, Popover, Select, SelectStateContext, SelectValue, Separator, useLocale } from "react-aria-components";
|
|
12
|
+
//#region src/_engines/react-aria/select.client.tsx
|
|
13
|
+
/**
|
|
14
|
+
* React Aria 1.22.1 resets the state on the form's `reset`, then the browser resets the hidden native
|
|
15
|
+
* <select> to its empty first option, and React never writes the value back (its prop did not
|
|
16
|
+
* change after the reset): the trigger showed the default while FormData had ''. After the browser's
|
|
17
|
+
* reset, the hidden <select> takes the state's value again.
|
|
18
|
+
*/
|
|
19
|
+
function FormReset({ root }) {
|
|
20
|
+
const value = use(SelectStateContext)?.value;
|
|
21
|
+
const latest = useRef(value);
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
latest.current = value;
|
|
24
|
+
});
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const select = root.current?.querySelector("select");
|
|
27
|
+
const form = select?.form;
|
|
28
|
+
const onReset = () => {
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
if (select) select.value = String(latest.current ?? "");
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
form?.addEventListener("reset", onReset);
|
|
34
|
+
return () => form?.removeEventListener("reset", onReset);
|
|
35
|
+
}, [root]);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const Root = ({ value, defaultValue, onValueChange, open, defaultOpen, onOpenChange, name, required, disabled, invalid, placeholder, "aria-label": label, "aria-labelledby": labelledBy, render, children, ...dom }) => {
|
|
39
|
+
const root = useRef(null);
|
|
40
|
+
return /* @__PURE__ */ jsx(EngineLocale, { children: /* @__PURE__ */ jsxs(Select, {
|
|
41
|
+
...defined({
|
|
42
|
+
value,
|
|
43
|
+
defaultValue,
|
|
44
|
+
isOpen: open,
|
|
45
|
+
defaultOpen,
|
|
46
|
+
name,
|
|
47
|
+
isRequired: required,
|
|
48
|
+
isDisabled: disabled,
|
|
49
|
+
isInvalid: invalid,
|
|
50
|
+
placeholder,
|
|
51
|
+
"aria-label": label,
|
|
52
|
+
"aria-labelledby": labelledBy
|
|
53
|
+
}),
|
|
54
|
+
onChange: (key) => onValueChange?.(key == null ? null : String(key), details("unknown")),
|
|
55
|
+
onOpenChange: (isOpen) => onOpenChange?.(isOpen, details("unknown")),
|
|
56
|
+
validationBehavior: "native",
|
|
57
|
+
render: (p, rp) => {
|
|
58
|
+
const s = {
|
|
59
|
+
open: rp.isOpen,
|
|
60
|
+
disabled: rp.isDisabled
|
|
61
|
+
};
|
|
62
|
+
const attrs = stateAttrs({
|
|
63
|
+
...s,
|
|
64
|
+
required: rp.isRequired,
|
|
65
|
+
invalid
|
|
66
|
+
});
|
|
67
|
+
return render(mergeProps(engineDom(p), dom, attrs, { ref: root }), s);
|
|
68
|
+
},
|
|
69
|
+
children: [children, /* @__PURE__ */ jsx(FormReset, { root })]
|
|
70
|
+
}) });
|
|
71
|
+
};
|
|
72
|
+
const Trigger = ({ id, "aria-label": label, "aria-describedby": describedBy, render, children, ...dom }) => {
|
|
73
|
+
const open = use(SelectStateContext)?.isOpen ?? false;
|
|
74
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
75
|
+
...defined({
|
|
76
|
+
id,
|
|
77
|
+
"aria-label": label,
|
|
78
|
+
"aria-describedby": describedBy
|
|
79
|
+
}),
|
|
80
|
+
render: (p, rp) => {
|
|
81
|
+
const s = {
|
|
82
|
+
open,
|
|
83
|
+
pressed: rp.isPressed && !open,
|
|
84
|
+
focusVisible: rp.isFocusVisible,
|
|
85
|
+
disabled: rp.isDisabled
|
|
86
|
+
};
|
|
87
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
88
|
+
},
|
|
89
|
+
children
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const Value = ({ render, children, ...dom }) => /* @__PURE__ */ jsx(SelectValue, {
|
|
93
|
+
render: (p, rp) => {
|
|
94
|
+
const s = { placeholder: rp.isPlaceholder };
|
|
95
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
96
|
+
},
|
|
97
|
+
children
|
|
98
|
+
});
|
|
99
|
+
const Popup = ({ side, align, sideOffset, container, list, render, children, ...dom }) => {
|
|
100
|
+
const { direction } = useLocale();
|
|
101
|
+
const popup = useRef(null);
|
|
102
|
+
const tabOut = useTabOut(popup);
|
|
103
|
+
const trigger = useTriggerRef();
|
|
104
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
105
|
+
placement: toPlacement(side, align),
|
|
106
|
+
offset: sideOffset,
|
|
107
|
+
...defined({ UNSTABLE_portalContainer: container ?? void 0 }),
|
|
108
|
+
render: (p, rp) => {
|
|
109
|
+
const s = {
|
|
110
|
+
entering: rp.isEntering,
|
|
111
|
+
exiting: rp.isExiting,
|
|
112
|
+
side: resolveSide(rp.placement, side, direction)
|
|
113
|
+
};
|
|
114
|
+
const engine = {
|
|
115
|
+
...engineDom(p),
|
|
116
|
+
style: popupStyle(p.style)
|
|
117
|
+
};
|
|
118
|
+
const attrs = stateAttrs({
|
|
119
|
+
open: !rp.isExiting,
|
|
120
|
+
...s,
|
|
121
|
+
align
|
|
122
|
+
});
|
|
123
|
+
return render(mergeProps(engine, dom, attrs, { ref: popup }), s);
|
|
124
|
+
},
|
|
125
|
+
children: [/* @__PURE__ */ jsx(ReturnFocus, {
|
|
126
|
+
trigger,
|
|
127
|
+
popup
|
|
128
|
+
}), /* @__PURE__ */ jsx(ListBox, {
|
|
129
|
+
render: (p) => createElement("div", mergeProps(engineDom(p), list, { onKeyDown: tabOut })),
|
|
130
|
+
children
|
|
131
|
+
})]
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const Item = ({ value, textValue, disabled, render, children, ...dom }) => /* @__PURE__ */ jsx(ListBoxItem, {
|
|
135
|
+
id: value,
|
|
136
|
+
...defined({
|
|
137
|
+
textValue,
|
|
138
|
+
isDisabled: disabled
|
|
139
|
+
}),
|
|
140
|
+
render: (p, rp) => {
|
|
141
|
+
const s = {
|
|
142
|
+
highlighted: rp.isFocused,
|
|
143
|
+
selected: rp.isSelected,
|
|
144
|
+
disabled: rp.isDisabled,
|
|
145
|
+
focusVisible: rp.isFocusVisible
|
|
146
|
+
};
|
|
147
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
148
|
+
},
|
|
149
|
+
children
|
|
150
|
+
});
|
|
151
|
+
const Group = ({ render, children, ...dom }) => /* @__PURE__ */ jsx(ListBoxSection, {
|
|
152
|
+
render: (p) => render(mergeProps(engineDom(p), dom), {}),
|
|
153
|
+
children
|
|
154
|
+
});
|
|
155
|
+
const GroupLabel = ({ render, children, ...dom }) => /* @__PURE__ */ jsx(Header, {
|
|
156
|
+
render: (p) => render(mergeProps(engineDom(p), dom), {}),
|
|
157
|
+
children
|
|
158
|
+
});
|
|
159
|
+
/** ARIA allows only option and group inside a listbox (axe aria-required-children), so the rule is
|
|
160
|
+
* presentational: React Aria's role="separator" and its orientation are dropped. */
|
|
161
|
+
function presentational(props) {
|
|
162
|
+
const { role: _role, "aria-orientation": _orientation, ...rest } = engineDom(props);
|
|
163
|
+
return {
|
|
164
|
+
...rest,
|
|
165
|
+
role: "presentation"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const Separator$1 = ({ render, ...dom }) => /* @__PURE__ */ jsx(Separator, { render: (p) => render(mergeProps(presentational(p), dom), {}) });
|
|
169
|
+
//#endregion
|
|
170
|
+
export { Group, GroupLabel, Item, Popup, Root, Separator$1 as Separator, Trigger, Value };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { mergeProps } from "../../contracts/merge-props.js";
|
|
3
|
+
import { details } from "../../contracts/details.js";
|
|
4
|
+
import { stateAttrs } from "../../contracts/state.js";
|
|
5
|
+
import { defined, engineDom } from "./dom.js";
|
|
6
|
+
import { EngineLocale } from "./locale.client.js";
|
|
7
|
+
import { createContext, use } from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
import { I18nProvider, Tab, TabList, TabPanel, Tabs, useLocale } from "react-aria-components";
|
|
10
|
+
//#region src/_engines/react-aria/tabs.client.tsx
|
|
11
|
+
/**
|
|
12
|
+
* The nearest Tabs root's `dir`, for its list. A root without `dir` inherits it, so a strip nested in
|
|
13
|
+
* the panel of a `dir` root keys the way CSS lays it out (the attribute is inherited). Unset at every
|
|
14
|
+
* level: the direction of the locale (EngineLocale).
|
|
15
|
+
*/
|
|
16
|
+
const Dir = createContext(void 0);
|
|
17
|
+
/**
|
|
18
|
+
* The arrows' direction (M26). React Aria has one direction input, the locale `I18nProvider` is
|
|
19
|
+
* given, and only the tab list reads it (`useTabList` and `useSelectableCollection`, for the
|
|
20
|
+
* arrows). So the list alone gets a locale whose direction is `dir`: the scope's locale when it
|
|
21
|
+
* already has that direction (every render without `dir` is unchanged), else `ar` or `en`. Nothing
|
|
22
|
+
* in the list formats, collates or speaks a string (the tabs delegate has no type-ahead), and each
|
|
23
|
+
* tab's content gets the scope's locale back (`Tab`), so that locale only carries the direction to
|
|
24
|
+
* the list's own hooks. A script subtag (`en-Arab-US`) would keep the language, but
|
|
25
|
+
* WebKit 27.2 derives `Intl.Locale` text direction from the language only and reads it as `ltr`.
|
|
26
|
+
* The provider is always rendered, so changing `dir` never remounts the list and drops focus.
|
|
27
|
+
*/
|
|
28
|
+
function Directed({ children }) {
|
|
29
|
+
const dir = use(Dir);
|
|
30
|
+
const { locale, direction } = useLocale();
|
|
31
|
+
return /* @__PURE__ */ jsx(I18nProvider, {
|
|
32
|
+
locale: dir === void 0 || dir === direction ? locale : dir === "rtl" ? "ar" : "en",
|
|
33
|
+
children
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const Root = ({ value, defaultValue, onValueChange, activationMode, disabled, render, children, ...dom }) => {
|
|
37
|
+
const inherited = use(Dir);
|
|
38
|
+
return /* @__PURE__ */ jsx(EngineLocale, { children: /* @__PURE__ */ jsx(Dir, {
|
|
39
|
+
value: dom.dir ?? inherited,
|
|
40
|
+
children: /* @__PURE__ */ jsx(Tabs, {
|
|
41
|
+
...defined({
|
|
42
|
+
selectedKey: value,
|
|
43
|
+
defaultSelectedKey: defaultValue,
|
|
44
|
+
isDisabled: disabled
|
|
45
|
+
}),
|
|
46
|
+
keyboardActivation: activationMode,
|
|
47
|
+
onSelectionChange: (key) => onValueChange?.(String(key), details("unknown")),
|
|
48
|
+
render: (p) => {
|
|
49
|
+
const s = { disabled: disabled === true };
|
|
50
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
51
|
+
},
|
|
52
|
+
children
|
|
53
|
+
})
|
|
54
|
+
}) });
|
|
55
|
+
};
|
|
56
|
+
const List = ({ "aria-label": label, "aria-labelledby": labelledBy, "aria-describedby": describedBy, render, children, ...dom }) => /* @__PURE__ */ jsx(Directed, { children: /* @__PURE__ */ jsx(TabList, {
|
|
57
|
+
...defined({
|
|
58
|
+
"aria-label": label,
|
|
59
|
+
"aria-labelledby": labelledBy,
|
|
60
|
+
"aria-describedby": describedBy
|
|
61
|
+
}),
|
|
62
|
+
render: (p, rp) => {
|
|
63
|
+
const s = { disabled: rp.state.isDisabled };
|
|
64
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
65
|
+
},
|
|
66
|
+
children
|
|
67
|
+
}) });
|
|
68
|
+
const Tab$1 = ({ value, disabled, render, children, ...dom }) => /* @__PURE__ */ jsx(Tab, {
|
|
69
|
+
id: value,
|
|
70
|
+
...defined({ isDisabled: disabled }),
|
|
71
|
+
render: (p, rp) => {
|
|
72
|
+
const s = {
|
|
73
|
+
selected: rp.isSelected,
|
|
74
|
+
disabled: rp.isDisabled,
|
|
75
|
+
focusVisible: rp.isFocusVisible
|
|
76
|
+
};
|
|
77
|
+
return render(mergeProps(engineDom(p), dom, stateAttrs(s)), s);
|
|
78
|
+
},
|
|
79
|
+
children: /* @__PURE__ */ jsx(EngineLocale, { children })
|
|
80
|
+
});
|
|
81
|
+
const Panel = ({ value, keepMounted, render, children, ...dom }) => /* @__PURE__ */ jsx(TabPanel, {
|
|
82
|
+
id: value,
|
|
83
|
+
shouldForceMount: keepMounted,
|
|
84
|
+
render: (p, rp) => {
|
|
85
|
+
const s = {
|
|
86
|
+
selected: !rp.isInert,
|
|
87
|
+
focusVisible: rp.isFocusVisible
|
|
88
|
+
};
|
|
89
|
+
const hidden = rp.isInert ? { hidden: true } : void 0;
|
|
90
|
+
return render(mergeProps(engineDom(p), dom, hidden, stateAttrs({ focusVisible: s.focusVisible })), s);
|
|
91
|
+
},
|
|
92
|
+
children
|
|
93
|
+
});
|
|
94
|
+
//#endregion
|
|
95
|
+
export { List, Panel, Root, Tab$1 as Tab };
|