@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.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/adapters/css.d.ts +33 -0
- package/dist/adapters/css.js +67 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.js +27 -0
- package/dist/adapters/tailwind.d.ts +25 -0
- package/dist/adapters/tailwind.js +122 -0
- package/dist/adapters/types.d.ts +49 -0
- package/dist/adapters/types.js +21 -0
- package/dist/adapters/unocss.d.ts +17 -0
- package/dist/adapters/unocss.js +107 -0
- package/dist/atoms/AspectRatio.d.ts +17 -0
- package/dist/atoms/AspectRatio.js +19 -0
- package/dist/atoms/Avatar.d.ts +23 -0
- package/dist/atoms/Avatar.js +46 -0
- package/dist/atoms/Badge.d.ts +25 -0
- package/dist/atoms/Badge.js +28 -0
- package/dist/atoms/Button.d.ts +58 -0
- package/dist/atoms/Button.js +67 -0
- package/dist/atoms/Checkbox.d.ts +41 -0
- package/dist/atoms/Checkbox.js +47 -0
- package/dist/atoms/Input.d.ts +36 -0
- package/dist/atoms/Input.js +50 -0
- package/dist/atoms/Kbd.d.ts +14 -0
- package/dist/atoms/Kbd.js +17 -0
- package/dist/atoms/Label.d.ts +24 -0
- package/dist/atoms/Label.js +26 -0
- package/dist/atoms/Marker.d.ts +42 -0
- package/dist/atoms/Marker.js +55 -0
- package/dist/atoms/NativeSelect.d.ts +43 -0
- package/dist/atoms/NativeSelect.js +81 -0
- package/dist/atoms/Progress.d.ts +21 -0
- package/dist/atoms/Progress.js +48 -0
- package/dist/atoms/ScrollArea.d.ts +28 -0
- package/dist/atoms/ScrollArea.js +37 -0
- package/dist/atoms/Separator.d.ts +16 -0
- package/dist/atoms/Separator.js +21 -0
- package/dist/atoms/Skeleton.d.ts +13 -0
- package/dist/atoms/Skeleton.js +18 -0
- package/dist/atoms/Slider.d.ts +30 -0
- package/dist/atoms/Slider.js +40 -0
- package/dist/atoms/Spinner.d.ts +17 -0
- package/dist/atoms/Spinner.js +20 -0
- package/dist/atoms/Switch.d.ts +24 -0
- package/dist/atoms/Switch.js +41 -0
- package/dist/atoms/Textarea.d.ts +28 -0
- package/dist/atoms/Textarea.js +36 -0
- package/dist/atoms/Toggle.d.ts +38 -0
- package/dist/atoms/Toggle.js +45 -0
- package/dist/atoms/index.d.ts +32 -0
- package/dist/atoms/index.js +32 -0
- package/dist/cli/add.d.ts +44 -0
- package/dist/cli/add.js +99 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/registry.d.ts +31 -0
- package/dist/cli/registry.js +83 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +43 -0
- package/dist/configure.d.ts +34 -0
- package/dist/configure.js +85 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/lib/children.d.ts +28 -0
- package/dist/lib/children.js +27 -0
- package/dist/lib/cn.d.ts +13 -0
- package/dist/lib/cn.js +13 -0
- package/dist/lib/cva.d.ts +83 -0
- package/dist/lib/cva.js +178 -0
- package/dist/lib/icons.d.ts +44 -0
- package/dist/lib/icons.js +79 -0
- package/dist/lib/id.d.ts +31 -0
- package/dist/lib/id.js +41 -0
- package/dist/lib/index.d.ts +12 -0
- package/dist/lib/index.js +12 -0
- package/dist/lib/motion.d.ts +31 -0
- package/dist/lib/motion.js +39 -0
- package/dist/lib/props.d.ts +48 -0
- package/dist/lib/props.js +59 -0
- package/dist/lib/styled.d.ts +31 -0
- package/dist/lib/styled.js +39 -0
- package/dist/molecules/Accordion.d.ts +35 -0
- package/dist/molecules/Accordion.js +117 -0
- package/dist/molecules/Alert.d.ts +30 -0
- package/dist/molecules/Alert.js +35 -0
- package/dist/molecules/Attachment.d.ts +39 -0
- package/dist/molecules/Attachment.js +103 -0
- package/dist/molecules/Breadcrumb.d.ts +29 -0
- package/dist/molecules/Breadcrumb.js +73 -0
- package/dist/molecules/Bubble.d.ts +56 -0
- package/dist/molecules/Bubble.js +92 -0
- package/dist/molecules/ButtonGroup.d.ts +23 -0
- package/dist/molecules/ButtonGroup.js +30 -0
- package/dist/molecules/Card.d.ts +17 -0
- package/dist/molecules/Card.js +17 -0
- package/dist/molecules/Collapsible.d.ts +40 -0
- package/dist/molecules/Collapsible.js +71 -0
- package/dist/molecules/Empty.d.ts +16 -0
- package/dist/molecules/Empty.js +16 -0
- package/dist/molecules/Field.d.ts +54 -0
- package/dist/molecules/Field.js +82 -0
- package/dist/molecules/InputGroup.d.ts +33 -0
- package/dist/molecules/InputGroup.js +35 -0
- package/dist/molecules/InputOTP.d.ts +33 -0
- package/dist/molecules/InputOTP.js +140 -0
- package/dist/molecules/Item.d.ts +41 -0
- package/dist/molecules/Item.js +48 -0
- package/dist/molecules/Message.d.ts +43 -0
- package/dist/molecules/Message.js +82 -0
- package/dist/molecules/Pagination.d.ts +41 -0
- package/dist/molecules/Pagination.js +144 -0
- package/dist/molecules/RadioGroup.d.ts +32 -0
- package/dist/molecules/RadioGroup.js +65 -0
- package/dist/molecules/Resizable.d.ts +37 -0
- package/dist/molecules/Resizable.js +136 -0
- package/dist/molecules/Table.d.ts +37 -0
- package/dist/molecules/Table.js +78 -0
- package/dist/molecules/Tabs.d.ts +37 -0
- package/dist/molecules/Tabs.js +109 -0
- package/dist/molecules/ToggleGroup.d.ts +37 -0
- package/dist/molecules/ToggleGroup.js +68 -0
- package/dist/molecules/Typography.d.ts +26 -0
- package/dist/molecules/Typography.js +67 -0
- package/dist/molecules/index.d.ts +37 -0
- package/dist/molecules/index.js +37 -0
- package/dist/organisms/AlertDialog.d.ts +40 -0
- package/dist/organisms/AlertDialog.js +104 -0
- package/dist/organisms/Calendar.d.ts +96 -0
- package/dist/organisms/Calendar.js +353 -0
- package/dist/organisms/Carousel.d.ts +29 -0
- package/dist/organisms/Carousel.js +128 -0
- package/dist/organisms/Chart.d.ts +55 -0
- package/dist/organisms/Chart.js +189 -0
- package/dist/organisms/Combobox.d.ts +41 -0
- package/dist/organisms/Combobox.js +102 -0
- package/dist/organisms/Command.d.ts +46 -0
- package/dist/organisms/Command.js +171 -0
- package/dist/organisms/CommandDialog.d.ts +42 -0
- package/dist/organisms/CommandDialog.js +116 -0
- package/dist/organisms/ContextMenu.d.ts +29 -0
- package/dist/organisms/ContextMenu.js +102 -0
- package/dist/organisms/DataTable.d.ts +50 -0
- package/dist/organisms/DataTable.js +257 -0
- package/dist/organisms/DatePicker.d.ts +34 -0
- package/dist/organisms/DatePicker.js +115 -0
- package/dist/organisms/DateRangePicker.d.ts +34 -0
- package/dist/organisms/DateRangePicker.js +122 -0
- package/dist/organisms/Dialog.d.ts +50 -0
- package/dist/organisms/Dialog.js +116 -0
- package/dist/organisms/Drawer.d.ts +40 -0
- package/dist/organisms/Drawer.js +131 -0
- package/dist/organisms/DropdownMenu.d.ts +30 -0
- package/dist/organisms/DropdownMenu.js +91 -0
- package/dist/organisms/Form.d.ts +114 -0
- package/dist/organisms/Form.js +151 -0
- package/dist/organisms/HoverCard.d.ts +30 -0
- package/dist/organisms/HoverCard.js +68 -0
- package/dist/organisms/Menubar.d.ts +28 -0
- package/dist/organisms/Menubar.js +147 -0
- package/dist/organisms/MessageScroller.d.ts +36 -0
- package/dist/organisms/MessageScroller.js +124 -0
- package/dist/organisms/NavigationMenu.d.ts +41 -0
- package/dist/organisms/NavigationMenu.js +139 -0
- package/dist/organisms/Popover.d.ts +33 -0
- package/dist/organisms/Popover.js +58 -0
- package/dist/organisms/Questionnaire.d.ts +74 -0
- package/dist/organisms/Questionnaire.js +235 -0
- package/dist/organisms/Select.d.ts +47 -0
- package/dist/organisms/Select.js +250 -0
- package/dist/organisms/Sheet.d.ts +34 -0
- package/dist/organisms/Sheet.js +107 -0
- package/dist/organisms/Sidebar.d.ts +100 -0
- package/dist/organisms/Sidebar.js +228 -0
- package/dist/organisms/Toaster.d.ts +67 -0
- package/dist/organisms/Toaster.js +189 -0
- package/dist/organisms/Tooltip.d.ts +39 -0
- package/dist/organisms/Tooltip.js +94 -0
- package/dist/organisms/index.d.ts +40 -0
- package/dist/organisms/index.js +40 -0
- package/dist/primitives/controllable.d.ts +38 -0
- package/dist/primitives/controllable.js +43 -0
- package/dist/primitives/dismissable.d.ts +53 -0
- package/dist/primitives/dismissable.js +158 -0
- package/dist/primitives/floating.d.ts +116 -0
- package/dist/primitives/floating.js +289 -0
- package/dist/primitives/floatingSurface.d.ts +56 -0
- package/dist/primitives/floatingSurface.js +142 -0
- package/dist/primitives/focusTrap.d.ts +47 -0
- package/dist/primitives/focusTrap.js +101 -0
- package/dist/primitives/focusable.d.ts +53 -0
- package/dist/primitives/focusable.js +109 -0
- package/dist/primitives/index.d.ts +27 -0
- package/dist/primitives/index.js +27 -0
- package/dist/primitives/menu.d.ts +102 -0
- package/dist/primitives/menu.js +356 -0
- package/dist/primitives/modalSurface.d.ts +54 -0
- package/dist/primitives/modalSurface.js +146 -0
- package/dist/primitives/portal.d.ts +34 -0
- package/dist/primitives/portal.js +42 -0
- package/dist/primitives/presence.d.ts +45 -0
- package/dist/primitives/presence.js +152 -0
- package/dist/primitives/rovingFocus.d.ts +47 -0
- package/dist/primitives/rovingFocus.js +176 -0
- package/dist/primitives/scrollLock.d.ts +21 -0
- package/dist/primitives/scrollLock.js +75 -0
- package/dist/primitives/typeahead.d.ts +39 -0
- package/dist/primitives/typeahead.js +123 -0
- package/dist/templates/AppShell.d.ts +30 -0
- package/dist/templates/AppShell.js +42 -0
- package/dist/templates/AuthLayout.d.ts +23 -0
- package/dist/templates/AuthLayout.js +42 -0
- package/dist/templates/SettingsLayout.d.ts +27 -0
- package/dist/templates/SettingsLayout.js +42 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.js +16 -0
- package/nebula.css +2 -0
- package/package.json +92 -0
- package/registry.json +1107 -0
- package/src/adapters/css.ts +75 -0
- package/src/adapters/index.ts +40 -0
- package/src/adapters/tailwind.ts +134 -0
- package/src/adapters/types.ts +53 -0
- package/src/adapters/unocss.ts +116 -0
- package/src/atoms/AspectRatio.ts +28 -0
- package/src/atoms/Avatar.ts +67 -0
- package/src/atoms/Badge.ts +46 -0
- package/src/atoms/Button.ts +96 -0
- package/src/atoms/Checkbox.ts +76 -0
- package/src/atoms/Input.ts +76 -0
- package/src/atoms/Kbd.ts +28 -0
- package/src/atoms/Label.ts +38 -0
- package/src/atoms/Marker.ts +79 -0
- package/src/atoms/NativeSelect.ts +115 -0
- package/src/atoms/Progress.ts +64 -0
- package/src/atoms/ScrollArea.ts +53 -0
- package/src/atoms/Separator.ts +36 -0
- package/src/atoms/Skeleton.ts +24 -0
- package/src/atoms/Slider.ts +65 -0
- package/src/atoms/Spinner.ts +28 -0
- package/src/atoms/Switch.ts +56 -0
- package/src/atoms/Textarea.ts +58 -0
- package/src/atoms/Toggle.ts +67 -0
- package/src/atoms/index.ts +62 -0
- package/src/cli/add.ts +126 -0
- package/src/cli/index.ts +22 -0
- package/src/cli/registry.ts +116 -0
- package/src/config.ts +102 -0
- package/src/configure.ts +127 -0
- package/src/index.ts +53 -0
- package/src/lib/children.ts +43 -0
- package/src/lib/cn.ts +14 -0
- package/src/lib/cva.ts +249 -0
- package/src/lib/icons.ts +138 -0
- package/src/lib/id.ts +44 -0
- package/src/lib/index.ts +21 -0
- package/src/lib/motion.ts +48 -0
- package/src/lib/props.ts +83 -0
- package/src/lib/styled.ts +60 -0
- package/src/molecules/Accordion.ts +161 -0
- package/src/molecules/Alert.ts +58 -0
- package/src/molecules/Attachment.ts +132 -0
- package/src/molecules/Breadcrumb.ts +107 -0
- package/src/molecules/Bubble.ts +134 -0
- package/src/molecules/ButtonGroup.ts +55 -0
- package/src/molecules/Card.ts +42 -0
- package/src/molecules/Collapsible.ts +89 -0
- package/src/molecules/Empty.ts +43 -0
- package/src/molecules/Field.ts +131 -0
- package/src/molecules/InputGroup.ts +62 -0
- package/src/molecules/InputOTP.ts +166 -0
- package/src/molecules/Item.ts +89 -0
- package/src/molecules/Message.ts +127 -0
- package/src/molecules/Pagination.ts +197 -0
- package/src/molecules/RadioGroup.ts +104 -0
- package/src/molecules/Resizable.ts +166 -0
- package/src/molecules/Table.ts +118 -0
- package/src/molecules/Tabs.ts +148 -0
- package/src/molecules/ToggleGroup.ts +109 -0
- package/src/molecules/Typography.ts +94 -0
- package/src/molecules/index.ts +144 -0
- package/src/organisms/AlertDialog.ts +133 -0
- package/src/organisms/Calendar.ts +470 -0
- package/src/organisms/Carousel.ts +161 -0
- package/src/organisms/Chart.ts +257 -0
- package/src/organisms/Combobox.ts +140 -0
- package/src/organisms/Command.ts +211 -0
- package/src/organisms/CommandDialog.ts +149 -0
- package/src/organisms/ContextMenu.ts +125 -0
- package/src/organisms/DataTable.ts +328 -0
- package/src/organisms/DatePicker.ts +141 -0
- package/src/organisms/DateRangePicker.ts +149 -0
- package/src/organisms/Dialog.ts +162 -0
- package/src/organisms/Drawer.ts +167 -0
- package/src/organisms/DropdownMenu.ts +112 -0
- package/src/organisms/Form.ts +217 -0
- package/src/organisms/HoverCard.ts +92 -0
- package/src/organisms/Menubar.ts +167 -0
- package/src/organisms/MessageScroller.ts +145 -0
- package/src/organisms/NavigationMenu.ts +184 -0
- package/src/organisms/Popover.ts +84 -0
- package/src/organisms/Questionnaire.ts +322 -0
- package/src/organisms/Select.ts +286 -0
- package/src/organisms/Sheet.ts +144 -0
- package/src/organisms/Sidebar.ts +336 -0
- package/src/organisms/Toaster.ts +246 -0
- package/src/organisms/Tooltip.ts +124 -0
- package/src/organisms/index.ts +134 -0
- package/src/primitives/controllable.ts +64 -0
- package/src/primitives/dismissable.ts +195 -0
- package/src/primitives/floating.ts +453 -0
- package/src/primitives/floatingSurface.ts +209 -0
- package/src/primitives/focusTrap.ts +132 -0
- package/src/primitives/focusable.ts +108 -0
- package/src/primitives/index.ts +88 -0
- package/src/primitives/menu.ts +491 -0
- package/src/primitives/modalSurface.ts +199 -0
- package/src/primitives/portal.ts +61 -0
- package/src/primitives/presence.ts +185 -0
- package/src/primitives/rovingFocus.ts +212 -0
- package/src/primitives/scrollLock.ts +79 -0
- package/src/primitives/typeahead.ts +156 -0
- package/src/templates/AppShell.ts +56 -0
- package/src/templates/AuthLayout.ts +66 -0
- package/src/templates/SettingsLayout.ts +63 -0
- package/src/templates/index.ts +21 -0
- 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;
|