@c9up/nebula 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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -0
  3. package/dist/adapters/css.d.ts +33 -0
  4. package/dist/adapters/css.js +67 -0
  5. package/dist/adapters/index.d.ts +16 -0
  6. package/dist/adapters/index.js +27 -0
  7. package/dist/adapters/tailwind.d.ts +25 -0
  8. package/dist/adapters/tailwind.js +122 -0
  9. package/dist/adapters/types.d.ts +49 -0
  10. package/dist/adapters/types.js +21 -0
  11. package/dist/adapters/unocss.d.ts +17 -0
  12. package/dist/adapters/unocss.js +107 -0
  13. package/dist/atoms/AspectRatio.d.ts +17 -0
  14. package/dist/atoms/AspectRatio.js +19 -0
  15. package/dist/atoms/Avatar.d.ts +23 -0
  16. package/dist/atoms/Avatar.js +46 -0
  17. package/dist/atoms/Badge.d.ts +25 -0
  18. package/dist/atoms/Badge.js +28 -0
  19. package/dist/atoms/Button.d.ts +58 -0
  20. package/dist/atoms/Button.js +67 -0
  21. package/dist/atoms/Checkbox.d.ts +41 -0
  22. package/dist/atoms/Checkbox.js +47 -0
  23. package/dist/atoms/Input.d.ts +36 -0
  24. package/dist/atoms/Input.js +50 -0
  25. package/dist/atoms/Kbd.d.ts +14 -0
  26. package/dist/atoms/Kbd.js +17 -0
  27. package/dist/atoms/Label.d.ts +24 -0
  28. package/dist/atoms/Label.js +26 -0
  29. package/dist/atoms/Marker.d.ts +42 -0
  30. package/dist/atoms/Marker.js +55 -0
  31. package/dist/atoms/NativeSelect.d.ts +43 -0
  32. package/dist/atoms/NativeSelect.js +81 -0
  33. package/dist/atoms/Progress.d.ts +21 -0
  34. package/dist/atoms/Progress.js +48 -0
  35. package/dist/atoms/ScrollArea.d.ts +28 -0
  36. package/dist/atoms/ScrollArea.js +37 -0
  37. package/dist/atoms/Separator.d.ts +16 -0
  38. package/dist/atoms/Separator.js +21 -0
  39. package/dist/atoms/Skeleton.d.ts +13 -0
  40. package/dist/atoms/Skeleton.js +18 -0
  41. package/dist/atoms/Slider.d.ts +30 -0
  42. package/dist/atoms/Slider.js +40 -0
  43. package/dist/atoms/Spinner.d.ts +17 -0
  44. package/dist/atoms/Spinner.js +20 -0
  45. package/dist/atoms/Switch.d.ts +24 -0
  46. package/dist/atoms/Switch.js +41 -0
  47. package/dist/atoms/Textarea.d.ts +28 -0
  48. package/dist/atoms/Textarea.js +36 -0
  49. package/dist/atoms/Toggle.d.ts +38 -0
  50. package/dist/atoms/Toggle.js +45 -0
  51. package/dist/atoms/index.d.ts +32 -0
  52. package/dist/atoms/index.js +32 -0
  53. package/dist/cli/add.d.ts +44 -0
  54. package/dist/cli/add.js +99 -0
  55. package/dist/cli/index.d.ts +10 -0
  56. package/dist/cli/index.js +10 -0
  57. package/dist/cli/registry.d.ts +31 -0
  58. package/dist/cli/registry.js +83 -0
  59. package/dist/config.d.ts +81 -0
  60. package/dist/config.js +43 -0
  61. package/dist/configure.d.ts +34 -0
  62. package/dist/configure.js +85 -0
  63. package/dist/index.d.ts +41 -0
  64. package/dist/index.js +41 -0
  65. package/dist/lib/children.d.ts +28 -0
  66. package/dist/lib/children.js +27 -0
  67. package/dist/lib/cn.d.ts +13 -0
  68. package/dist/lib/cn.js +13 -0
  69. package/dist/lib/cva.d.ts +83 -0
  70. package/dist/lib/cva.js +178 -0
  71. package/dist/lib/icons.d.ts +44 -0
  72. package/dist/lib/icons.js +79 -0
  73. package/dist/lib/id.d.ts +31 -0
  74. package/dist/lib/id.js +41 -0
  75. package/dist/lib/index.d.ts +12 -0
  76. package/dist/lib/index.js +12 -0
  77. package/dist/lib/motion.d.ts +31 -0
  78. package/dist/lib/motion.js +39 -0
  79. package/dist/lib/props.d.ts +48 -0
  80. package/dist/lib/props.js +59 -0
  81. package/dist/lib/styled.d.ts +31 -0
  82. package/dist/lib/styled.js +39 -0
  83. package/dist/molecules/Accordion.d.ts +35 -0
  84. package/dist/molecules/Accordion.js +117 -0
  85. package/dist/molecules/Alert.d.ts +30 -0
  86. package/dist/molecules/Alert.js +35 -0
  87. package/dist/molecules/Attachment.d.ts +39 -0
  88. package/dist/molecules/Attachment.js +103 -0
  89. package/dist/molecules/Breadcrumb.d.ts +29 -0
  90. package/dist/molecules/Breadcrumb.js +73 -0
  91. package/dist/molecules/Bubble.d.ts +56 -0
  92. package/dist/molecules/Bubble.js +92 -0
  93. package/dist/molecules/ButtonGroup.d.ts +23 -0
  94. package/dist/molecules/ButtonGroup.js +30 -0
  95. package/dist/molecules/Card.d.ts +17 -0
  96. package/dist/molecules/Card.js +17 -0
  97. package/dist/molecules/Collapsible.d.ts +40 -0
  98. package/dist/molecules/Collapsible.js +71 -0
  99. package/dist/molecules/Empty.d.ts +16 -0
  100. package/dist/molecules/Empty.js +16 -0
  101. package/dist/molecules/Field.d.ts +54 -0
  102. package/dist/molecules/Field.js +82 -0
  103. package/dist/molecules/InputGroup.d.ts +33 -0
  104. package/dist/molecules/InputGroup.js +35 -0
  105. package/dist/molecules/InputOTP.d.ts +33 -0
  106. package/dist/molecules/InputOTP.js +140 -0
  107. package/dist/molecules/Item.d.ts +41 -0
  108. package/dist/molecules/Item.js +48 -0
  109. package/dist/molecules/Message.d.ts +43 -0
  110. package/dist/molecules/Message.js +82 -0
  111. package/dist/molecules/Pagination.d.ts +41 -0
  112. package/dist/molecules/Pagination.js +144 -0
  113. package/dist/molecules/RadioGroup.d.ts +32 -0
  114. package/dist/molecules/RadioGroup.js +65 -0
  115. package/dist/molecules/Resizable.d.ts +37 -0
  116. package/dist/molecules/Resizable.js +136 -0
  117. package/dist/molecules/Table.d.ts +37 -0
  118. package/dist/molecules/Table.js +78 -0
  119. package/dist/molecules/Tabs.d.ts +37 -0
  120. package/dist/molecules/Tabs.js +109 -0
  121. package/dist/molecules/ToggleGroup.d.ts +37 -0
  122. package/dist/molecules/ToggleGroup.js +68 -0
  123. package/dist/molecules/Typography.d.ts +26 -0
  124. package/dist/molecules/Typography.js +67 -0
  125. package/dist/molecules/index.d.ts +37 -0
  126. package/dist/molecules/index.js +37 -0
  127. package/dist/organisms/AlertDialog.d.ts +40 -0
  128. package/dist/organisms/AlertDialog.js +104 -0
  129. package/dist/organisms/Calendar.d.ts +96 -0
  130. package/dist/organisms/Calendar.js +353 -0
  131. package/dist/organisms/Carousel.d.ts +29 -0
  132. package/dist/organisms/Carousel.js +128 -0
  133. package/dist/organisms/Chart.d.ts +55 -0
  134. package/dist/organisms/Chart.js +189 -0
  135. package/dist/organisms/Combobox.d.ts +41 -0
  136. package/dist/organisms/Combobox.js +102 -0
  137. package/dist/organisms/Command.d.ts +46 -0
  138. package/dist/organisms/Command.js +171 -0
  139. package/dist/organisms/CommandDialog.d.ts +42 -0
  140. package/dist/organisms/CommandDialog.js +116 -0
  141. package/dist/organisms/ContextMenu.d.ts +29 -0
  142. package/dist/organisms/ContextMenu.js +102 -0
  143. package/dist/organisms/DataTable.d.ts +50 -0
  144. package/dist/organisms/DataTable.js +257 -0
  145. package/dist/organisms/DatePicker.d.ts +34 -0
  146. package/dist/organisms/DatePicker.js +115 -0
  147. package/dist/organisms/DateRangePicker.d.ts +34 -0
  148. package/dist/organisms/DateRangePicker.js +122 -0
  149. package/dist/organisms/Dialog.d.ts +50 -0
  150. package/dist/organisms/Dialog.js +116 -0
  151. package/dist/organisms/Drawer.d.ts +40 -0
  152. package/dist/organisms/Drawer.js +131 -0
  153. package/dist/organisms/DropdownMenu.d.ts +30 -0
  154. package/dist/organisms/DropdownMenu.js +91 -0
  155. package/dist/organisms/Form.d.ts +114 -0
  156. package/dist/organisms/Form.js +151 -0
  157. package/dist/organisms/HoverCard.d.ts +30 -0
  158. package/dist/organisms/HoverCard.js +68 -0
  159. package/dist/organisms/Menubar.d.ts +28 -0
  160. package/dist/organisms/Menubar.js +147 -0
  161. package/dist/organisms/MessageScroller.d.ts +36 -0
  162. package/dist/organisms/MessageScroller.js +124 -0
  163. package/dist/organisms/NavigationMenu.d.ts +41 -0
  164. package/dist/organisms/NavigationMenu.js +139 -0
  165. package/dist/organisms/Popover.d.ts +33 -0
  166. package/dist/organisms/Popover.js +58 -0
  167. package/dist/organisms/Questionnaire.d.ts +74 -0
  168. package/dist/organisms/Questionnaire.js +235 -0
  169. package/dist/organisms/Select.d.ts +47 -0
  170. package/dist/organisms/Select.js +250 -0
  171. package/dist/organisms/Sheet.d.ts +34 -0
  172. package/dist/organisms/Sheet.js +107 -0
  173. package/dist/organisms/Sidebar.d.ts +100 -0
  174. package/dist/organisms/Sidebar.js +228 -0
  175. package/dist/organisms/Toaster.d.ts +67 -0
  176. package/dist/organisms/Toaster.js +189 -0
  177. package/dist/organisms/Tooltip.d.ts +39 -0
  178. package/dist/organisms/Tooltip.js +94 -0
  179. package/dist/organisms/index.d.ts +40 -0
  180. package/dist/organisms/index.js +40 -0
  181. package/dist/primitives/controllable.d.ts +38 -0
  182. package/dist/primitives/controllable.js +43 -0
  183. package/dist/primitives/dismissable.d.ts +53 -0
  184. package/dist/primitives/dismissable.js +158 -0
  185. package/dist/primitives/floating.d.ts +116 -0
  186. package/dist/primitives/floating.js +289 -0
  187. package/dist/primitives/floatingSurface.d.ts +56 -0
  188. package/dist/primitives/floatingSurface.js +142 -0
  189. package/dist/primitives/focusTrap.d.ts +47 -0
  190. package/dist/primitives/focusTrap.js +101 -0
  191. package/dist/primitives/focusable.d.ts +53 -0
  192. package/dist/primitives/focusable.js +109 -0
  193. package/dist/primitives/index.d.ts +27 -0
  194. package/dist/primitives/index.js +27 -0
  195. package/dist/primitives/menu.d.ts +102 -0
  196. package/dist/primitives/menu.js +356 -0
  197. package/dist/primitives/modalSurface.d.ts +54 -0
  198. package/dist/primitives/modalSurface.js +146 -0
  199. package/dist/primitives/portal.d.ts +34 -0
  200. package/dist/primitives/portal.js +42 -0
  201. package/dist/primitives/presence.d.ts +45 -0
  202. package/dist/primitives/presence.js +152 -0
  203. package/dist/primitives/rovingFocus.d.ts +47 -0
  204. package/dist/primitives/rovingFocus.js +176 -0
  205. package/dist/primitives/scrollLock.d.ts +21 -0
  206. package/dist/primitives/scrollLock.js +75 -0
  207. package/dist/primitives/typeahead.d.ts +39 -0
  208. package/dist/primitives/typeahead.js +123 -0
  209. package/dist/templates/AppShell.d.ts +30 -0
  210. package/dist/templates/AppShell.js +42 -0
  211. package/dist/templates/AuthLayout.d.ts +23 -0
  212. package/dist/templates/AuthLayout.js +42 -0
  213. package/dist/templates/SettingsLayout.d.ts +27 -0
  214. package/dist/templates/SettingsLayout.js +42 -0
  215. package/dist/templates/index.d.ts +16 -0
  216. package/dist/templates/index.js +16 -0
  217. package/nebula.css +2 -0
  218. package/package.json +92 -0
  219. package/registry.json +1107 -0
  220. package/src/adapters/css.ts +75 -0
  221. package/src/adapters/index.ts +40 -0
  222. package/src/adapters/tailwind.ts +134 -0
  223. package/src/adapters/types.ts +53 -0
  224. package/src/adapters/unocss.ts +116 -0
  225. package/src/atoms/AspectRatio.ts +28 -0
  226. package/src/atoms/Avatar.ts +67 -0
  227. package/src/atoms/Badge.ts +46 -0
  228. package/src/atoms/Button.ts +96 -0
  229. package/src/atoms/Checkbox.ts +76 -0
  230. package/src/atoms/Input.ts +76 -0
  231. package/src/atoms/Kbd.ts +28 -0
  232. package/src/atoms/Label.ts +38 -0
  233. package/src/atoms/Marker.ts +79 -0
  234. package/src/atoms/NativeSelect.ts +115 -0
  235. package/src/atoms/Progress.ts +64 -0
  236. package/src/atoms/ScrollArea.ts +53 -0
  237. package/src/atoms/Separator.ts +36 -0
  238. package/src/atoms/Skeleton.ts +24 -0
  239. package/src/atoms/Slider.ts +65 -0
  240. package/src/atoms/Spinner.ts +28 -0
  241. package/src/atoms/Switch.ts +56 -0
  242. package/src/atoms/Textarea.ts +58 -0
  243. package/src/atoms/Toggle.ts +67 -0
  244. package/src/atoms/index.ts +62 -0
  245. package/src/cli/add.ts +126 -0
  246. package/src/cli/index.ts +22 -0
  247. package/src/cli/registry.ts +116 -0
  248. package/src/config.ts +102 -0
  249. package/src/configure.ts +127 -0
  250. package/src/index.ts +53 -0
  251. package/src/lib/children.ts +43 -0
  252. package/src/lib/cn.ts +14 -0
  253. package/src/lib/cva.ts +249 -0
  254. package/src/lib/icons.ts +138 -0
  255. package/src/lib/id.ts +44 -0
  256. package/src/lib/index.ts +21 -0
  257. package/src/lib/motion.ts +48 -0
  258. package/src/lib/props.ts +83 -0
  259. package/src/lib/styled.ts +60 -0
  260. package/src/molecules/Accordion.ts +161 -0
  261. package/src/molecules/Alert.ts +58 -0
  262. package/src/molecules/Attachment.ts +132 -0
  263. package/src/molecules/Breadcrumb.ts +107 -0
  264. package/src/molecules/Bubble.ts +134 -0
  265. package/src/molecules/ButtonGroup.ts +55 -0
  266. package/src/molecules/Card.ts +42 -0
  267. package/src/molecules/Collapsible.ts +89 -0
  268. package/src/molecules/Empty.ts +43 -0
  269. package/src/molecules/Field.ts +131 -0
  270. package/src/molecules/InputGroup.ts +62 -0
  271. package/src/molecules/InputOTP.ts +166 -0
  272. package/src/molecules/Item.ts +89 -0
  273. package/src/molecules/Message.ts +127 -0
  274. package/src/molecules/Pagination.ts +197 -0
  275. package/src/molecules/RadioGroup.ts +104 -0
  276. package/src/molecules/Resizable.ts +166 -0
  277. package/src/molecules/Table.ts +118 -0
  278. package/src/molecules/Tabs.ts +148 -0
  279. package/src/molecules/ToggleGroup.ts +109 -0
  280. package/src/molecules/Typography.ts +94 -0
  281. package/src/molecules/index.ts +144 -0
  282. package/src/organisms/AlertDialog.ts +133 -0
  283. package/src/organisms/Calendar.ts +470 -0
  284. package/src/organisms/Carousel.ts +161 -0
  285. package/src/organisms/Chart.ts +257 -0
  286. package/src/organisms/Combobox.ts +140 -0
  287. package/src/organisms/Command.ts +211 -0
  288. package/src/organisms/CommandDialog.ts +149 -0
  289. package/src/organisms/ContextMenu.ts +125 -0
  290. package/src/organisms/DataTable.ts +328 -0
  291. package/src/organisms/DatePicker.ts +141 -0
  292. package/src/organisms/DateRangePicker.ts +149 -0
  293. package/src/organisms/Dialog.ts +162 -0
  294. package/src/organisms/Drawer.ts +167 -0
  295. package/src/organisms/DropdownMenu.ts +112 -0
  296. package/src/organisms/Form.ts +217 -0
  297. package/src/organisms/HoverCard.ts +92 -0
  298. package/src/organisms/Menubar.ts +167 -0
  299. package/src/organisms/MessageScroller.ts +145 -0
  300. package/src/organisms/NavigationMenu.ts +184 -0
  301. package/src/organisms/Popover.ts +84 -0
  302. package/src/organisms/Questionnaire.ts +322 -0
  303. package/src/organisms/Select.ts +286 -0
  304. package/src/organisms/Sheet.ts +144 -0
  305. package/src/organisms/Sidebar.ts +336 -0
  306. package/src/organisms/Toaster.ts +246 -0
  307. package/src/organisms/Tooltip.ts +124 -0
  308. package/src/organisms/index.ts +134 -0
  309. package/src/primitives/controllable.ts +64 -0
  310. package/src/primitives/dismissable.ts +195 -0
  311. package/src/primitives/floating.ts +453 -0
  312. package/src/primitives/floatingSurface.ts +209 -0
  313. package/src/primitives/focusTrap.ts +132 -0
  314. package/src/primitives/focusable.ts +108 -0
  315. package/src/primitives/index.ts +88 -0
  316. package/src/primitives/menu.ts +491 -0
  317. package/src/primitives/modalSurface.ts +199 -0
  318. package/src/primitives/portal.ts +61 -0
  319. package/src/primitives/presence.ts +185 -0
  320. package/src/primitives/rovingFocus.ts +212 -0
  321. package/src/primitives/scrollLock.ts +79 -0
  322. package/src/primitives/typeahead.ts +156 -0
  323. package/src/templates/AppShell.ts +56 -0
  324. package/src/templates/AuthLayout.ts +66 -0
  325. package/src/templates/SettingsLayout.ts +63 -0
  326. package/src/templates/index.ts +21 -0
  327. package/theme.css +151 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Tooltip — a label that appears on hover or focus.
3
+ *
4
+ * Three rules separate a usable tooltip from an irritating one, and all three
5
+ * are about time rather than appearance:
6
+ *
7
+ * - An **open delay**, so moving the pointer across a toolbar does not fire
8
+ * six tooltips on the way past.
9
+ * - A **close delay**, so travelling from the trigger to the tooltip — which a
10
+ * user does to read a long one, or click a link inside — does not dismiss it
11
+ * halfway.
12
+ * - **No delay between neighbours.** Once one tooltip is showing, the next one
13
+ * appears instantly; the user has clearly decided to browse the toolbar, and
14
+ * re-serving the delay each time makes the interface feel stuck. That is why
15
+ * `lastClosedAt` is module state and not per-component.
16
+ *
17
+ * Focus opens it with no delay at all. A keyboard user landing on a control
18
+ * has already committed to it, and a delay just looks like lag.
19
+ *
20
+ * `role="tooltip"` plus `aria-describedby` — never `aria-labelledby`. A
21
+ * tooltip supplements a control's name; it is not the name. A button labelled
22
+ * only by its tooltip is unlabelled to anything that does not hover.
23
+ */
24
+ import { component, html, onUnmount, signal } from "@c9up/aurora";
25
+ import { slot } from "../lib/children.js";
26
+ import { cn } from "../lib/cn.js";
27
+ import { uid } from "../lib/id.js";
28
+ import { fadeInOut } from "../lib/motion.js";
29
+ import { read } from "../lib/props.js";
30
+ import { floatingSurface } from "../primitives/floatingSurface.js";
31
+ const OPEN_DELAY_MS = 500;
32
+ const CLOSE_DELAY_MS = 150;
33
+ /** How long after one closes that the next opens instantly. */
34
+ const GRACE_MS = 300;
35
+ /** Shared across every tooltip on the page — see the "no delay" rule above. */
36
+ let lastClosedAt = 0;
37
+ export const Tooltip = component((props) => {
38
+ const triggerId = uid("tooltip-trigger");
39
+ const contentId = uid("tooltip-content");
40
+ const open = signal(false);
41
+ let timer;
42
+ function clear() {
43
+ if (timer !== undefined)
44
+ clearTimeout(timer);
45
+ timer = undefined;
46
+ }
47
+ function scheduleOpen(immediate) {
48
+ clear();
49
+ const withinGrace = Date.now() - lastClosedAt < GRACE_MS;
50
+ const delay = immediate || withinGrace ? 0 : (props.openDelay ?? OPEN_DELAY_MS);
51
+ if (delay === 0) {
52
+ open(true);
53
+ return;
54
+ }
55
+ timer = setTimeout(() => open(true), delay);
56
+ }
57
+ function scheduleClose() {
58
+ clear();
59
+ timer = setTimeout(() => {
60
+ open(false);
61
+ lastClosedAt = Date.now();
62
+ }, props.closeDelay ?? CLOSE_DELAY_MS);
63
+ }
64
+ onUnmount(clear);
65
+ floatingSurface({
66
+ anchor: () => document.getElementById(triggerId),
67
+ open: () => open(),
68
+ onClose: () => {
69
+ clear();
70
+ open(false);
71
+ },
72
+ placement: props.placement ?? "top",
73
+ offset: 6,
74
+ outsidePointer: false,
75
+ content: () => html `<div
76
+ data-slot="tooltip-content"
77
+ id="${contentId}"
78
+ role="tooltip"
79
+ class="${cn("bg-primary text-primary-foreground z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance", fadeInOut, read(props.contentClass))}"
80
+ @pointerenter="${clear}"
81
+ @pointerleave="${scheduleClose}"
82
+ >${props.content}</div>`,
83
+ });
84
+ return html `<span
85
+ data-slot="tooltip-trigger"
86
+ id="${triggerId}"
87
+ aria-describedby="${() => (open() ? contentId : undefined)}"
88
+ class="${() => cn("inline-flex", read(props.class))}"
89
+ @pointerenter="${() => scheduleOpen(false)}"
90
+ @pointerleave="${scheduleClose}"
91
+ @focusin="${() => scheduleOpen(true)}"
92
+ @focusout="${scheduleClose}"
93
+ >${slot(props.trigger)}</span>`;
94
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Organisms — components that escape their own DOM position.
3
+ *
4
+ * The line that separates these from molecules: an organism portals, traps
5
+ * focus, floats against an anchor, or coordinates several molecules into one
6
+ * working unit. Dialog is an organism though its markup is simpler than
7
+ * Table's, because it renders into `<body>` and takes over the page while it
8
+ * is open.
9
+ *
10
+ * Almost all of them are assembled from two shared surfaces —
11
+ * `floatingSurface` for the anchored overlays, `modalSurface` for the modal
12
+ * ones — so the parts that are easy to get wrong (teardown order, focus
13
+ * restoration, hiding the page from assistive technology) exist once.
14
+ */
15
+ export { AlertDialog, type AlertDialogProps } from "./AlertDialog.js";
16
+ export { addDays, addMonths, Calendar, type CalendarProps, type DateRange, isSameDay, monthGrid, startOfDay, } from "./Calendar.js";
17
+ export { Carousel, type CarouselProps } from "./Carousel.js";
18
+ export { Chart, type ChartDatum, type ChartProps, type ChartSeries, valueRange, } from "./Chart.js";
19
+ export { Combobox, type ComboboxOption, type ComboboxProps, } from "./Combobox.js";
20
+ export { Command, type CommandItem, type CommandProps, defaultFilter, } from "./Command.js";
21
+ export { CommandDialog, type CommandDialogProps } from "./CommandDialog.js";
22
+ export { ContextMenu, type ContextMenuProps } from "./ContextMenu.js";
23
+ export { type Column, DataTable, type DataTableProps } from "./DataTable.js";
24
+ export { DatePicker, type DatePickerProps, toISODate } from "./DatePicker.js";
25
+ export { DateRangePicker, type DateRangePickerProps, } from "./DateRangePicker.js";
26
+ export { Dialog, type DialogProps, dialogBackdropClasses, dialogPanelClasses, useDialog, } from "./Dialog.js";
27
+ export { Drawer, type DrawerProps } from "./Drawer.js";
28
+ export { DropdownMenu, type DropdownMenuProps } from "./DropdownMenu.js";
29
+ export { bind, type FieldBinding, Form, type FormProps, SubmitButton, type SubmitButtonProps, TextAreaField, type TextAreaFieldProps, TextField, type TextFieldProps, } from "./Form.js";
30
+ export { HoverCard, type HoverCardProps } from "./HoverCard.js";
31
+ export { Menubar, type MenubarMenu, type MenubarProps } from "./Menubar.js";
32
+ export { MessageScroller, type MessageScrollerProps, } from "./MessageScroller.js";
33
+ export { type NavigationItem, type NavigationLink, NavigationMenu, type NavigationMenuProps, } from "./NavigationMenu.js";
34
+ export { Popover, type PopoverProps, popoverContentClasses, } from "./Popover.js";
35
+ export { type Answer, type Answers, type FreeformQuestion, isAnswered, type MultipleChoiceQuestion, type Question, Questionnaire, type QuestionnaireProps, type QuestionOption, type SingleChoiceQuestion, } from "./Questionnaire.js";
36
+ export { Select, type SelectOption, type SelectProps, selectTriggerClasses, } from "./Select.js";
37
+ export { Sheet, type SheetProps } from "./Sheet.js";
38
+ export { Sidebar, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuItem, type SidebarMenuItemProps, SidebarMenuSub, SidebarMenuSubItem, type SidebarProps, SidebarTrigger, type SidebarTriggerProps, sidebarCollapsed, sidebarState, } from "./Sidebar.js";
39
+ export { Toaster, type ToasterProps, type ToastOptions, type ToastVariant, toast, } from "./Toaster.js";
40
+ export { Tooltip, type TooltipProps } from "./Tooltip.js";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Organisms — components that escape their own DOM position.
3
+ *
4
+ * The line that separates these from molecules: an organism portals, traps
5
+ * focus, floats against an anchor, or coordinates several molecules into one
6
+ * working unit. Dialog is an organism though its markup is simpler than
7
+ * Table's, because it renders into `<body>` and takes over the page while it
8
+ * is open.
9
+ *
10
+ * Almost all of them are assembled from two shared surfaces —
11
+ * `floatingSurface` for the anchored overlays, `modalSurface` for the modal
12
+ * ones — so the parts that are easy to get wrong (teardown order, focus
13
+ * restoration, hiding the page from assistive technology) exist once.
14
+ */
15
+ export { AlertDialog } from "./AlertDialog.js";
16
+ export { addDays, addMonths, Calendar, isSameDay, monthGrid, startOfDay, } from "./Calendar.js";
17
+ export { Carousel } from "./Carousel.js";
18
+ export { Chart, valueRange, } from "./Chart.js";
19
+ export { Combobox, } from "./Combobox.js";
20
+ export { Command, defaultFilter, } from "./Command.js";
21
+ export { CommandDialog } from "./CommandDialog.js";
22
+ export { ContextMenu } from "./ContextMenu.js";
23
+ export { DataTable } from "./DataTable.js";
24
+ export { DatePicker, toISODate } from "./DatePicker.js";
25
+ export { DateRangePicker, } from "./DateRangePicker.js";
26
+ export { Dialog, dialogBackdropClasses, dialogPanelClasses, useDialog, } from "./Dialog.js";
27
+ export { Drawer } from "./Drawer.js";
28
+ export { DropdownMenu } from "./DropdownMenu.js";
29
+ export { bind, Form, SubmitButton, TextAreaField, TextField, } from "./Form.js";
30
+ export { HoverCard } from "./HoverCard.js";
31
+ export { Menubar } from "./Menubar.js";
32
+ export { MessageScroller, } from "./MessageScroller.js";
33
+ export { NavigationMenu, } from "./NavigationMenu.js";
34
+ export { Popover, popoverContentClasses, } from "./Popover.js";
35
+ export { isAnswered, Questionnaire, } from "./Questionnaire.js";
36
+ export { Select, selectTriggerClasses, } from "./Select.js";
37
+ export { Sheet } from "./Sheet.js";
38
+ export { Sidebar, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubItem, SidebarTrigger, sidebarCollapsed, sidebarState, } from "./Sidebar.js";
39
+ export { Toaster, toast, } from "./Toaster.js";
40
+ export { Tooltip } from "./Tooltip.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Controlled / uncontrolled state.
3
+ *
4
+ * Every stateful shadcn component takes the same trio: `open` (the caller
5
+ * owns the state), `defaultOpen` (the component owns it) and `onOpenChange`
6
+ * (told either way). Radix folds that into `useControllableState`; this is the
7
+ * Aurora equivalent, and the reason a Dialog can be opened either by its own
8
+ * trigger or by a signal somewhere else in the app without changing shape.
9
+ *
10
+ * // uncontrolled — nebula holds the state
11
+ * Dialog({ defaultOpen: false })
12
+ *
13
+ * // controlled — the app holds it, nebula only asks
14
+ * const open = signal(false)
15
+ * Dialog({ open, onOpenChange: open })
16
+ *
17
+ * The rule is one line: a `value` that reads as anything other than
18
+ * `undefined` wins over the internal signal, on every read. Deciding once at
19
+ * setup instead would freeze a component whose prop only becomes defined after
20
+ * an async load.
21
+ */
22
+ import { type ReadSignal } from "@c9up/aurora";
23
+ import { type Reactive } from "../lib/props.js";
24
+ export interface ControllableOptions<T> {
25
+ /** The caller's value. Present means controlled. */
26
+ value?: Reactive<T | undefined>;
27
+ /** Starting value when uncontrolled. */
28
+ initial: T;
29
+ /** Notified on every change, controlled or not. */
30
+ onChange?: (next: T) => void;
31
+ }
32
+ export interface Controllable<T> {
33
+ /** Current value — the caller's when controlled, ours otherwise. */
34
+ readonly current: ReadSignal<T>;
35
+ /** Request a change. Always notifies; only writes internally when uncontrolled. */
36
+ set(next: T): void;
37
+ }
38
+ export declare function controllable<T>(options: ControllableOptions<T>): Controllable<T>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Controlled / uncontrolled state.
3
+ *
4
+ * Every stateful shadcn component takes the same trio: `open` (the caller
5
+ * owns the state), `defaultOpen` (the component owns it) and `onOpenChange`
6
+ * (told either way). Radix folds that into `useControllableState`; this is the
7
+ * Aurora equivalent, and the reason a Dialog can be opened either by its own
8
+ * trigger or by a signal somewhere else in the app without changing shape.
9
+ *
10
+ * // uncontrolled — nebula holds the state
11
+ * Dialog({ defaultOpen: false })
12
+ *
13
+ * // controlled — the app holds it, nebula only asks
14
+ * const open = signal(false)
15
+ * Dialog({ open, onOpenChange: open })
16
+ *
17
+ * The rule is one line: a `value` that reads as anything other than
18
+ * `undefined` wins over the internal signal, on every read. Deciding once at
19
+ * setup instead would freeze a component whose prop only becomes defined after
20
+ * an async load.
21
+ */
22
+ import { memo, signal } from "@c9up/aurora";
23
+ import { read } from "../lib/props.js";
24
+ export function controllable(options) {
25
+ const internal = signal(options.initial);
26
+ const current = memo(() => {
27
+ if (options.value === undefined)
28
+ return internal();
29
+ const external = read(options.value);
30
+ return external === undefined ? internal() : external;
31
+ });
32
+ return {
33
+ current,
34
+ set(next) {
35
+ // Written even when controlled. The caller may ignore `onChange` and
36
+ // leave its own state put — a "cancel" on a confirm dialog does exactly
37
+ // that — and the internal value has to stay in step for the moment the
38
+ // caller stops controlling it.
39
+ internal(next);
40
+ options.onChange?.(next);
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Dismissable layers — Escape, click-outside and focus-outside, stacked.
3
+ *
4
+ * Every transient surface needs this: Popover, DropdownMenu, ContextMenu,
5
+ * Select, Combobox, Tooltip, Dialog, Sheet, Drawer. Wiring it per component
6
+ * is how you end up with a menu inside a dialog where Escape closes both, or
7
+ * a submenu whose parent collapses the moment you click into it.
8
+ *
9
+ * So layers live in one stack and the rules are applied once, globally:
10
+ *
11
+ * - **Escape** reaches only the topmost layer that accepts it. That is what
12
+ * makes Escape close a submenu first and its parent menu second.
13
+ * - **Pointer outside** walks the stack top-down and stops at the first layer
14
+ * the event landed inside. Layers above it close, layers below it stay. A
15
+ * click in a parent menu therefore closes the submenu and nothing else.
16
+ * - **Focus outside** follows the same walk, but only layers that opt in — a
17
+ * modal dialog holds focus by design and must not close because focus moved.
18
+ *
19
+ * Layers are portalled, so a nested surface is not a DOM descendant of its
20
+ * parent. Containment is answered per layer by its own element plus whatever
21
+ * it excludes (its trigger, almost always), never by tree position.
22
+ */
23
+ export type DismissReason = "escape" | "outside-pointer" | "outside-focus";
24
+ export interface DismissableOptions {
25
+ /** The surface itself. Read lazily — it mounts after the layer registers. */
26
+ element: () => HTMLElement | null;
27
+ /** Called when this layer should close. */
28
+ onDismiss: (reason: DismissReason) => void;
29
+ /**
30
+ * Extra elements that do not count as "outside". The trigger belongs here:
31
+ * without it, pressing the trigger to close would dismiss on pointerdown and
32
+ * immediately reopen on click.
33
+ */
34
+ exclude?: () => ReadonlyArray<HTMLElement | null | undefined>;
35
+ /** Escape dismisses this layer when it is topmost. Default `true`. */
36
+ escapeKey?: boolean;
37
+ /** A pointer press outside dismisses. Default `true`. */
38
+ outsidePointer?: boolean;
39
+ /** Focus moving outside dismisses. Default `false`. */
40
+ outsideFocus?: boolean;
41
+ }
42
+ export interface DismissableLayer {
43
+ /** Remove this layer from the stack. Safe to call twice. */
44
+ remove(): void;
45
+ }
46
+ /**
47
+ * Register a dismissable layer. The returned handle must be removed when the
48
+ * surface unmounts — a stale layer keeps answering Escape for a surface that
49
+ * is no longer on screen.
50
+ */
51
+ export declare function dismissable(options: DismissableOptions): DismissableLayer;
52
+ /** How many layers are currently open. Used by the Escape/scroll-lock tests. */
53
+ export declare function layerCount(): number;
@@ -0,0 +1,158 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Dismissable layers — Escape, click-outside and focus-outside, stacked.
4
+ *
5
+ * Every transient surface needs this: Popover, DropdownMenu, ContextMenu,
6
+ * Select, Combobox, Tooltip, Dialog, Sheet, Drawer. Wiring it per component
7
+ * is how you end up with a menu inside a dialog where Escape closes both, or
8
+ * a submenu whose parent collapses the moment you click into it.
9
+ *
10
+ * So layers live in one stack and the rules are applied once, globally:
11
+ *
12
+ * - **Escape** reaches only the topmost layer that accepts it. That is what
13
+ * makes Escape close a submenu first and its parent menu second.
14
+ * - **Pointer outside** walks the stack top-down and stops at the first layer
15
+ * the event landed inside. Layers above it close, layers below it stay. A
16
+ * click in a parent menu therefore closes the submenu and nothing else.
17
+ * - **Focus outside** follows the same walk, but only layers that opt in — a
18
+ * modal dialog holds focus by design and must not close because focus moved.
19
+ *
20
+ * Layers are portalled, so a nested surface is not a DOM descendant of its
21
+ * parent. Containment is answered per layer by its own element plus whatever
22
+ * it excludes (its trigger, almost always), never by tree position.
23
+ */
24
+ const stack = [];
25
+ let nextLayerId = 0;
26
+ let listening = false;
27
+ /**
28
+ * Register a dismissable layer. The returned handle must be removed when the
29
+ * surface unmounts — a stale layer keeps answering Escape for a surface that
30
+ * is no longer on screen.
31
+ */
32
+ export function dismissable(options) {
33
+ nextLayerId += 1;
34
+ const layer = { ...options, id: nextLayerId };
35
+ stack.push(layer);
36
+ startListening();
37
+ let removed = false;
38
+ return {
39
+ remove() {
40
+ if (removed)
41
+ return;
42
+ removed = true;
43
+ const index = stack.findIndex((entry) => entry.id === layer.id);
44
+ if (index !== -1)
45
+ stack.splice(index, 1);
46
+ stopListeningIfIdle();
47
+ },
48
+ };
49
+ }
50
+ /** How many layers are currently open. Used by the Escape/scroll-lock tests. */
51
+ export function layerCount() {
52
+ return stack.length;
53
+ }
54
+ // ─── global listeners ────────────────────────────────────────────────
55
+ /**
56
+ * Listeners attach on the first layer and detach with the last, rather than
57
+ * living for the lifetime of the page. An app that never opens an overlay
58
+ * pays nothing, and a test that opens and closes one leaves no residue.
59
+ */
60
+ function startListening() {
61
+ if (listening || typeof document === "undefined")
62
+ return;
63
+ listening = true;
64
+ document.addEventListener("keydown", onKeyDown, true);
65
+ document.addEventListener("pointerdown", onPointerDown, true);
66
+ document.addEventListener("focusin", onFocusIn, true);
67
+ }
68
+ function stopListeningIfIdle() {
69
+ if (!listening || stack.length > 0)
70
+ return;
71
+ listening = false;
72
+ document.removeEventListener("keydown", onKeyDown, true);
73
+ document.removeEventListener("pointerdown", onPointerDown, true);
74
+ document.removeEventListener("focusin", onFocusIn, true);
75
+ }
76
+ function onKeyDown(event) {
77
+ if (event.key !== "Escape")
78
+ return;
79
+ // Topmost layer that accepts Escape — not simply the topmost layer. A
80
+ // Tooltip stacked over a Popover opts out, and Escape must then reach the
81
+ // Popover rather than being swallowed.
82
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
83
+ const layer = stack[i];
84
+ if (layer === undefined)
85
+ continue;
86
+ if (layer.escapeKey === false)
87
+ continue;
88
+ event.stopPropagation();
89
+ layer.onDismiss("escape");
90
+ return;
91
+ }
92
+ }
93
+ function onPointerDown(event) {
94
+ dismissOutside(event, "outside-pointer");
95
+ }
96
+ function onFocusIn(event) {
97
+ dismissOutside(event, "outside-focus");
98
+ }
99
+ /**
100
+ * Close every layer the event landed outside of, stopping at the first one it
101
+ * landed inside.
102
+ *
103
+ * The snapshot is taken before dismissing: `onDismiss` mutates the stack, and
104
+ * iterating the live array while it shrinks skips layers.
105
+ */
106
+ function dismissOutside(event, reason) {
107
+ const path = eventPath(event);
108
+ const snapshot = stack.slice();
109
+ for (let i = snapshot.length - 1; i >= 0; i -= 1) {
110
+ const layer = snapshot[i];
111
+ if (layer === undefined)
112
+ continue;
113
+ if (containsEvent(layer, path))
114
+ return;
115
+ const optedIn = reason === "outside-pointer"
116
+ ? layer.outsidePointer !== false
117
+ : layer.outsideFocus === true;
118
+ // A layer that opted out still blocks: closing the one beneath it while
119
+ // it stays open would leave the stack inverted on screen.
120
+ if (!optedIn)
121
+ return;
122
+ layer.onDismiss(reason);
123
+ }
124
+ }
125
+ /** Did the event originate inside this layer, or inside anything it excludes? */
126
+ function containsEvent(layer, path) {
127
+ const element = layer.element();
128
+ if (element !== null && path.includes(element))
129
+ return true;
130
+ const excluded = layer.exclude?.() ?? [];
131
+ for (const candidate of excluded) {
132
+ if (candidate && path.includes(candidate))
133
+ return true;
134
+ }
135
+ return false;
136
+ }
137
+ /**
138
+ * The nodes an event travelled through.
139
+ *
140
+ * `composedPath()` is what makes containment correct across a shadow root,
141
+ * where `event.target` is retargeted to the host and a naive `contains()`
142
+ * check reports the wrong answer. Not every DOM implementation nebula runs
143
+ * against provides it, so the ancestor walk stands in when it is missing.
144
+ */
145
+ function eventPath(event) {
146
+ if (typeof event.composedPath === "function") {
147
+ const path = event.composedPath();
148
+ if (path.length > 0)
149
+ return path;
150
+ }
151
+ const walked = [];
152
+ let node = event.target instanceof Node ? event.target : null;
153
+ while (node !== null) {
154
+ walked.push(node);
155
+ node = node.parentNode;
156
+ }
157
+ return walked;
158
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Floating placement — where a popover, menu, select or tooltip goes.
3
+ *
4
+ * This is the piece Radix delegates to `@floating-ui/dom`. nebula computes it
5
+ * instead, because the alternative is a runtime dependency in a workspace
6
+ * whose stated position is that `cn` was worth reimplementing rather than
7
+ * pulling in clsx and tailwind-merge.
8
+ *
9
+ * Scope is chosen to make that defensible. Floating UI is a general middleware
10
+ * pipeline; this is the four behaviours the shadcn component set actually
11
+ * uses — `offset`, `flip`, `shift`, and arrow centring — plus the available
12
+ * height a scrollable menu needs for its `max-height`. No middleware
13
+ * abstraction, no virtual elements, no `autoPlacement`.
14
+ *
15
+ * The geometry lives in `resolvePosition`, a pure function over rectangles.
16
+ * Everything that touches the DOM — measuring, writing styles, watching for
17
+ * scroll — is in `autoPosition` around it. That split is what lets the flip
18
+ * and shift rules be tested exhaustively without a browser, which matters
19
+ * because they are where placement bugs actually live.
20
+ *
21
+ * Coordinates are viewport-relative and meant for `position: fixed`. Fixed
22
+ * positioning is what lets a portalled surface ignore every transformed or
23
+ * clipping ancestor between it and the root; an absolutely positioned popover
24
+ * has to find an offset parent, and gets it wrong the moment one appears.
25
+ */
26
+ export type Side = "top" | "right" | "bottom" | "left";
27
+ export type Align = "start" | "center" | "end";
28
+ export type Placement = Side | `${Side}-${Align}`;
29
+ /** Just the fields of a DOMRect the maths needs. */
30
+ export interface Rect {
31
+ readonly x: number;
32
+ readonly y: number;
33
+ readonly width: number;
34
+ readonly height: number;
35
+ }
36
+ export interface Viewport {
37
+ readonly width: number;
38
+ readonly height: number;
39
+ }
40
+ export interface PositionOptions {
41
+ /** Preferred placement. Default `"bottom"`. */
42
+ placement?: Placement;
43
+ /** Gap between anchor and surface, in px. Default `4`. */
44
+ offset?: number;
45
+ /** Keep this much clearance from the viewport edge. Default `8`. */
46
+ padding?: number;
47
+ /** Flip to the opposite side when the preferred one does not fit. Default `true`. */
48
+ flip?: boolean;
49
+ /** Slide along the cross axis to stay on screen. Default `true`. */
50
+ shift?: boolean;
51
+ /** Arrow size in px; centres the arrow and keeps it off the corners. */
52
+ arrowSize?: number;
53
+ /**
54
+ * Mirror the placement for a right-to-left writing direction.
55
+ *
56
+ * Placements are written in physical terms — `"right-start"` for a submenu —
57
+ * because that is what reads clearly at the call site and what CSS ends up
58
+ * applying. In Arabic or Hebrew those names mean the wrong edge: a submenu
59
+ * belongs on the *inline end*, which is the left. Rather than making every
60
+ * component reason in logical directions, the mirror happens once, here.
61
+ *
62
+ * `autoPosition` reads it off the anchor's computed direction, so a component
63
+ * never has to pass it.
64
+ */
65
+ rtl?: boolean;
66
+ }
67
+ export interface Position {
68
+ readonly x: number;
69
+ readonly y: number;
70
+ /** Placement actually used — may differ from the request after a flip. */
71
+ readonly placement: Placement;
72
+ readonly side: Side;
73
+ readonly align: Align;
74
+ /**
75
+ * Space between the surface's edge and the viewport on the resolved side.
76
+ * Menus bind it to `max-height` so a long list scrolls instead of
77
+ * overflowing off screen.
78
+ */
79
+ readonly availableHeight: number;
80
+ /** Arrow offset along the surface's cross axis, when `arrowSize` was given. */
81
+ readonly arrow?: {
82
+ readonly x?: number;
83
+ readonly y?: number;
84
+ };
85
+ }
86
+ /**
87
+ * Compute where the surface goes. Pure — no DOM, no side effects.
88
+ *
89
+ * Order matters and matches Floating UI's: offset, then flip, then shift.
90
+ * Shifting before flipping would slide a surface halfway off the anchor to
91
+ * make room on a side that should have been abandoned entirely.
92
+ */
93
+ export declare function resolvePosition(anchor: Rect, floating: Rect, viewport: Viewport, options?: PositionOptions): Position;
94
+ export interface AutoPositionOptions extends PositionOptions {
95
+ /** The arrow element to position, if the surface has one. */
96
+ arrow?: () => HTMLElement | null;
97
+ /** Force the surface to the anchor's width. Select and Combobox use it. */
98
+ matchWidth?: boolean;
99
+ /** Called after each reposition — for `data-side` attributes and the like. */
100
+ onPositioned?: (position: Position) => void;
101
+ }
102
+ export interface AutoPosition {
103
+ /** Recompute and rewrite the styles now. */
104
+ update(): void;
105
+ /** Stop watching. Styles already written are left in place. */
106
+ stop(): void;
107
+ }
108
+ /**
109
+ * Position `floating` against `anchor` and keep it there.
110
+ *
111
+ * Updates run on scroll (captured, so ancestor scroll containers are covered
112
+ * without walking the tree to find them) and on resize, plus whenever either
113
+ * element changes size where `ResizeObserver` exists. A menu whose content
114
+ * loads in and grows would otherwise stay positioned for its old height.
115
+ */
116
+ export declare function autoPosition(anchor: HTMLElement, floating: HTMLElement, options?: AutoPositionOptions): AutoPosition;