@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.
Files changed (258) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +120 -2
  3. package/THIRD_PARTY_NOTICES +61 -0
  4. package/dist/_engines/react-aria/dialog.client.js +134 -0
  5. package/dist/_engines/react-aria/dom-allowlist.js +19 -0
  6. package/dist/_engines/react-aria/dom.js +31 -0
  7. package/dist/_engines/react-aria/locale.client.js +13 -0
  8. package/dist/_engines/react-aria/menu.client.js +225 -0
  9. package/dist/_engines/react-aria/overlay.client.js +41 -0
  10. package/dist/_engines/react-aria/popup.client.js +127 -0
  11. package/dist/_engines/react-aria/select.client.js +170 -0
  12. package/dist/_engines/react-aria/tabs.client.js +95 -0
  13. package/dist/_engines/react-aria/tooltip.client.js +72 -0
  14. package/dist/_internal/cx.js +11 -0
  15. package/dist/_internal/engine-part.d.ts +1 -0
  16. package/dist/_internal/field.js +20 -0
  17. package/dist/_internal/glyphs.js +34 -0
  18. package/dist/_internal/keys.js +62 -0
  19. package/dist/_internal/overlay.d.ts +9 -0
  20. package/dist/_virtual/_rolldown/runtime.js +13 -0
  21. package/dist/alert/alert-dismiss.client.d.ts +32 -0
  22. package/dist/alert/alert-dismiss.client.js +36 -0
  23. package/dist/alert/alert.d.ts +28 -0
  24. package/dist/alert/alert.js +44 -0
  25. package/dist/alert/index.d.ts +3 -0
  26. package/dist/alert/index.js +3 -0
  27. package/dist/augment.d.ts +13 -0
  28. package/dist/augment.js +0 -0
  29. package/dist/badge/badge.d.ts +19 -0
  30. package/dist/badge/badge.js +22 -0
  31. package/dist/badge/index.d.ts +2 -0
  32. package/dist/badge/index.js +1 -0
  33. package/dist/button/button.d.ts +54 -0
  34. package/dist/button/button.js +62 -0
  35. package/dist/button/index.d.ts +2 -0
  36. package/dist/button/index.js +1 -0
  37. package/dist/card/card.d.ts +57 -0
  38. package/dist/card/card.js +62 -0
  39. package/dist/card/card.parts.d.ts +6 -0
  40. package/dist/card/card.parts.js +14 -0
  41. package/dist/card/index.d.ts +3 -0
  42. package/dist/card/index.js +3 -0
  43. package/dist/checkbox/checkbox.client.d.ts +37 -0
  44. package/dist/checkbox/checkbox.client.js +81 -0
  45. package/dist/checkbox/index.d.ts +2 -0
  46. package/dist/checkbox/index.js +1 -0
  47. package/dist/config/config.client.d.ts +9 -0
  48. package/dist/config/config.client.js +18 -0
  49. package/dist/config/index.d.ts +2 -0
  50. package/dist/config/index.js +1 -0
  51. package/dist/container/container.d.ts +13 -0
  52. package/dist/container/container.js +16 -0
  53. package/dist/container/index.d.ts +2 -0
  54. package/dist/container/index.js +1 -0
  55. package/dist/contracts/details.d.ts +13 -0
  56. package/dist/contracts/details.js +9 -0
  57. package/dist/contracts/index.d.ts +5 -0
  58. package/dist/contracts/index.js +5 -0
  59. package/dist/contracts/merge-props.d.ts +16 -0
  60. package/dist/contracts/merge-props.js +76 -0
  61. package/dist/contracts/render.d.ts +17 -0
  62. package/dist/contracts/render.js +43 -0
  63. package/dist/contracts/state.d.ts +42 -0
  64. package/dist/contracts/state.js +42 -0
  65. package/dist/css/alert.css +2 -0
  66. package/dist/css/badge.css +2 -0
  67. package/dist/css/base.css +2 -0
  68. package/dist/css/button.css +2 -0
  69. package/dist/css/card.css +2 -0
  70. package/dist/css/checkbox.css +2 -0
  71. package/dist/css/container.css +2 -0
  72. package/dist/css/delta.css +2 -0
  73. package/dist/css/dialog.css +2 -0
  74. package/dist/css/empty-state.css +2 -0
  75. package/dist/css/field.css +2 -0
  76. package/dist/css/fieldset.css +2 -0
  77. package/dist/css/grid.css +2 -0
  78. package/dist/css/heading.css +2 -0
  79. package/dist/css/icon-button.css +2 -0
  80. package/dist/css/image.css +2 -0
  81. package/dist/css/inline-error.css +2 -0
  82. package/dist/css/inline.css +2 -0
  83. package/dist/css/input.css +2 -0
  84. package/dist/css/kbd.css +2 -0
  85. package/dist/css/menu.css +2 -0
  86. package/dist/css/meter.css +2 -0
  87. package/dist/css/native-select.css +2 -0
  88. package/dist/css/progress-bar.css +2 -0
  89. package/dist/css/progress-ring.css +2 -0
  90. package/dist/css/radio-group.css +2 -0
  91. package/dist/css/segmented-control.css +2 -0
  92. package/dist/css/select.css +2 -0
  93. package/dist/css/separator.css +2 -0
  94. package/dist/css/skeleton.css +2 -0
  95. package/dist/css/stack.css +2 -0
  96. package/dist/css/switch.css +2 -0
  97. package/dist/css/tab-nav.css +2 -0
  98. package/dist/css/tabs.css +2 -0
  99. package/dist/css/text.css +2 -0
  100. package/dist/css/textarea.css +2 -0
  101. package/dist/css/tooltip.css +2 -0
  102. package/dist/delta/delta.d.ts +32 -0
  103. package/dist/delta/delta.js +50 -0
  104. package/dist/delta/index.d.ts +2 -0
  105. package/dist/delta/index.js +1 -0
  106. package/dist/dialog/dialog.client.d.ts +25 -0
  107. package/dist/dialog/dialog.client.js +167 -0
  108. package/dist/dialog/dialog.contract.d.ts +97 -0
  109. package/dist/dialog/dialog.parts.d.ts +6 -0
  110. package/dist/dialog/dialog.parts.js +15 -0
  111. package/dist/dialog/index.d.ts +5 -0
  112. package/dist/dialog/index.js +3 -0
  113. package/dist/empty-state/empty-state.d.ts +18 -0
  114. package/dist/empty-state/empty-state.js +32 -0
  115. package/dist/empty-state/index.d.ts +2 -0
  116. package/dist/empty-state/index.js +1 -0
  117. package/dist/field/field-scope.client.d.ts +50 -0
  118. package/dist/field/field-scope.client.js +205 -0
  119. package/dist/field/field-static.d.ts +122 -0
  120. package/dist/field/field-static.js +159 -0
  121. package/dist/field/field.d.ts +5 -0
  122. package/dist/field/field.js +53 -0
  123. package/dist/field/index.d.ts +4 -0
  124. package/dist/field/index.js +4 -0
  125. package/dist/fieldset/fieldset.d.ts +36 -0
  126. package/dist/fieldset/fieldset.js +63 -0
  127. package/dist/fieldset/index.d.ts +2 -0
  128. package/dist/fieldset/index.js +1 -0
  129. package/dist/grid/grid.d.ts +38 -0
  130. package/dist/grid/grid.js +28 -0
  131. package/dist/grid/index.d.ts +2 -0
  132. package/dist/grid/index.js +1 -0
  133. package/dist/heading/heading.d.ts +27 -0
  134. package/dist/heading/heading.js +26 -0
  135. package/dist/heading/index.d.ts +2 -0
  136. package/dist/heading/index.js +1 -0
  137. package/dist/icon-button/icon-button-static.d.ts +23 -0
  138. package/dist/icon-button/icon-button-static.js +83 -0
  139. package/dist/icon-button/icon-button.client.js +29 -0
  140. package/dist/icon-button/icon-button.contract.d.ts +89 -0
  141. package/dist/icon-button/icon-button.d.ts +5 -0
  142. package/dist/icon-button/icon-button.js +21 -0
  143. package/dist/icon-button/index.d.ts +4 -0
  144. package/dist/icon-button/index.js +3 -0
  145. package/dist/image/image.d.ts +28 -0
  146. package/dist/image/image.js +24 -0
  147. package/dist/image/index.d.ts +2 -0
  148. package/dist/image/index.js +1 -0
  149. package/dist/index.d.ts +95 -0
  150. package/dist/index.js +51 -0
  151. package/dist/inline/index.d.ts +2 -0
  152. package/dist/inline/index.js +1 -0
  153. package/dist/inline/inline.d.ts +16 -0
  154. package/dist/inline/inline.js +14 -0
  155. package/dist/inline-error/index.d.ts +2 -0
  156. package/dist/inline-error/index.js +1 -0
  157. package/dist/inline-error/inline-error.d.ts +17 -0
  158. package/dist/inline-error/inline-error.js +33 -0
  159. package/dist/input/index.d.ts +2 -0
  160. package/dist/input/index.js +1 -0
  161. package/dist/input/input.client.d.ts +39 -0
  162. package/dist/input/input.client.js +82 -0
  163. package/dist/kbd/index.d.ts +2 -0
  164. package/dist/kbd/index.js +1 -0
  165. package/dist/kbd/kbd.d.ts +19 -0
  166. package/dist/kbd/kbd.js +68 -0
  167. package/dist/menu/index.d.ts +5 -0
  168. package/dist/menu/index.js +3 -0
  169. package/dist/menu/menu.client.d.ts +14 -0
  170. package/dist/menu/menu.client.js +152 -0
  171. package/dist/menu/menu.contract.d.ts +152 -0
  172. package/dist/menu/menu.parts.d.ts +6 -0
  173. package/dist/menu/menu.parts.js +17 -0
  174. package/dist/meter/index.d.ts +2 -0
  175. package/dist/meter/index.js +1 -0
  176. package/dist/meter/meter.d.ts +43 -0
  177. package/dist/meter/meter.js +59 -0
  178. package/dist/native-select/index.d.ts +2 -0
  179. package/dist/native-select/index.js +1 -0
  180. package/dist/native-select/native-select.d.ts +43 -0
  181. package/dist/native-select/native-select.js +56 -0
  182. package/dist/progress-bar/index.d.ts +2 -0
  183. package/dist/progress-bar/index.js +1 -0
  184. package/dist/progress-bar/progress-bar.d.ts +34 -0
  185. package/dist/progress-bar/progress-bar.js +55 -0
  186. package/dist/progress-ring/index.d.ts +2 -0
  187. package/dist/progress-ring/index.js +1 -0
  188. package/dist/progress-ring/progress-ring.d.ts +38 -0
  189. package/dist/progress-ring/progress-ring.js +67 -0
  190. package/dist/radio-group/index.d.ts +3 -0
  191. package/dist/radio-group/index.js +3 -0
  192. package/dist/radio-group/radio-group.client.d.ts +22 -0
  193. package/dist/radio-group/radio-group.client.js +70 -0
  194. package/dist/radio-group/radio-group.d.ts +28 -0
  195. package/dist/radio-group/radio-group.js +35 -0
  196. package/dist/segmented-control/index.d.ts +3 -0
  197. package/dist/segmented-control/index.js +3 -0
  198. package/dist/segmented-control/segmented-control.client.d.ts +61 -0
  199. package/dist/segmented-control/segmented-control.client.js +156 -0
  200. package/dist/segmented-control/segmented-control.parts.d.ts +6 -0
  201. package/dist/segmented-control/segmented-control.parts.js +9 -0
  202. package/dist/select/index.d.ts +5 -0
  203. package/dist/select/index.js +3 -0
  204. package/dist/select/select.client.d.ts +12 -0
  205. package/dist/select/select.client.js +152 -0
  206. package/dist/select/select.contract.d.ts +119 -0
  207. package/dist/select/select.parts.d.ts +6 -0
  208. package/dist/select/select.parts.js +15 -0
  209. package/dist/separator/index.d.ts +2 -0
  210. package/dist/separator/index.js +1 -0
  211. package/dist/separator/separator.d.ts +26 -0
  212. package/dist/separator/separator.js +23 -0
  213. package/dist/skeleton/index.d.ts +2 -0
  214. package/dist/skeleton/index.js +1 -0
  215. package/dist/skeleton/skeleton.d.ts +20 -0
  216. package/dist/skeleton/skeleton.js +19 -0
  217. package/dist/stack/index.d.ts +2 -0
  218. package/dist/stack/index.js +1 -0
  219. package/dist/stack/stack.d.ts +17 -0
  220. package/dist/stack/stack.js +14 -0
  221. package/dist/styles.css +536 -0
  222. package/dist/switch/index.d.ts +2 -0
  223. package/dist/switch/index.js +1 -0
  224. package/dist/switch/switch.client.d.ts +33 -0
  225. package/dist/switch/switch.client.js +63 -0
  226. package/dist/tab-nav/index.d.ts +3 -0
  227. package/dist/tab-nav/index.js +3 -0
  228. package/dist/tab-nav/tab-nav.d.ts +36 -0
  229. package/dist/tab-nav/tab-nav.js +47 -0
  230. package/dist/tab-nav/tab-nav.parts.d.ts +6 -0
  231. package/dist/tab-nav/tab-nav.parts.js +9 -0
  232. package/dist/tabs/index.d.ts +4 -0
  233. package/dist/tabs/index.js +3 -0
  234. package/dist/tabs/tabs.client.d.ts +8 -0
  235. package/dist/tabs/tabs.client.js +68 -0
  236. package/dist/tabs/tabs.contract.d.ts +89 -0
  237. package/dist/tabs/tabs.parts.d.ts +6 -0
  238. package/dist/tabs/tabs.parts.js +11 -0
  239. package/dist/text/index.d.ts +2 -0
  240. package/dist/text/index.js +1 -0
  241. package/dist/text/text.d.ts +21 -0
  242. package/dist/text/text.js +17 -0
  243. package/dist/textarea/index.d.ts +2 -0
  244. package/dist/textarea/index.js +1 -0
  245. package/dist/textarea/textarea.client.d.ts +18 -0
  246. package/dist/textarea/textarea.client.js +31 -0
  247. package/dist/theme-script/index.d.ts +2 -0
  248. package/dist/theme-script/index.js +1 -0
  249. package/dist/theme-script/theme-script.d.ts +43 -0
  250. package/dist/theme-script/theme-script.js +46 -0
  251. package/dist/tooltip/index.d.ts +5 -0
  252. package/dist/tooltip/index.js +3 -0
  253. package/dist/tooltip/tooltip.client.d.ts +12 -0
  254. package/dist/tooltip/tooltip.client.js +51 -0
  255. package/dist/tooltip/tooltip.contract.d.ts +60 -0
  256. package/dist/tooltip/tooltip.parts.d.ts +6 -0
  257. package/dist/tooltip/tooltip.parts.js +10 -0
  258. 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 };