ajo-ui 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 (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +126 -0
  3. package/dist/accordion.js +130 -0
  4. package/dist/avatar.js +54 -0
  5. package/dist/calendar.js +2 -0
  6. package/dist/carousel.js +239 -0
  7. package/dist/chart.js +790 -0
  8. package/dist/checkbox-group.js +70 -0
  9. package/dist/checkbox.js +77 -0
  10. package/dist/chunks/bar-CVafh6C1.js +99 -0
  11. package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
  12. package/dist/chunks/collection-DtRB63U4.js +111 -0
  13. package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
  14. package/dist/chunks/menu-B45IyHHC.js +704 -0
  15. package/dist/chunks/native-BJdhd9XJ.js +20 -0
  16. package/dist/chunks/popup-C8Bb3l_g.js +459 -0
  17. package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
  18. package/dist/chunks/position-D6_i_SRn.js +434 -0
  19. package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
  20. package/dist/collapsible.js +106 -0
  21. package/dist/command.js +263 -0
  22. package/dist/context-menu.js +112 -0
  23. package/dist/data-table.js +5 -0
  24. package/dist/dialog.js +207 -0
  25. package/dist/direction.js +22 -0
  26. package/dist/drawer.js +139 -0
  27. package/dist/field.js +26 -0
  28. package/dist/index.js +38 -0
  29. package/dist/input-date.js +994 -0
  30. package/dist/input-group.js +52 -0
  31. package/dist/input-otp.js +179 -0
  32. package/dist/menu.js +2 -0
  33. package/dist/menubar.js +236 -0
  34. package/dist/message-scroller.js +446 -0
  35. package/dist/navigation-menu.js +330 -0
  36. package/dist/popover.js +307 -0
  37. package/dist/progress.js +39 -0
  38. package/dist/radio-group.js +107 -0
  39. package/dist/resizable.js +172 -0
  40. package/dist/select.js +961 -0
  41. package/dist/sidebar.js +343 -0
  42. package/dist/slider.js +259 -0
  43. package/dist/switch.js +53 -0
  44. package/dist/tabs.js +182 -0
  45. package/dist/toast.js +492 -0
  46. package/dist/toggle-group.js +111 -0
  47. package/dist/toggle.js +52 -0
  48. package/dist/toolbar.js +127 -0
  49. package/dist/tooltip.js +196 -0
  50. package/dist/utils.js +104 -0
  51. package/dist/virtual-list.js +2 -0
  52. package/package.json +250 -0
  53. package/src/accordion.tsx +261 -0
  54. package/src/availability.ts +261 -0
  55. package/src/avatar.tsx +99 -0
  56. package/src/bar.ts +156 -0
  57. package/src/calendar.tsx +1441 -0
  58. package/src/carousel.tsx +424 -0
  59. package/src/chart.tsx +1194 -0
  60. package/src/checkbox-group.tsx +132 -0
  61. package/src/checkbox.tsx +130 -0
  62. package/src/collapsible.tsx +188 -0
  63. package/src/collection.ts +154 -0
  64. package/src/command.tsx +511 -0
  65. package/src/context-menu.tsx +233 -0
  66. package/src/data-table-contract.ts +143 -0
  67. package/src/data-table-model.ts +760 -0
  68. package/src/data-table.tsx +475 -0
  69. package/src/dialog.tsx +393 -0
  70. package/src/direction.tsx +45 -0
  71. package/src/drawer.tsx +251 -0
  72. package/src/field.tsx +61 -0
  73. package/src/index.ts +37 -0
  74. package/src/input-date.tsx +1539 -0
  75. package/src/input-group.tsx +142 -0
  76. package/src/input-otp.tsx +324 -0
  77. package/src/menu-cluster.ts +124 -0
  78. package/src/menu.tsx +1095 -0
  79. package/src/menubar.tsx +459 -0
  80. package/src/message-scroller.tsx +732 -0
  81. package/src/native.ts +26 -0
  82. package/src/navigation-menu.tsx +578 -0
  83. package/src/popover.tsx +519 -0
  84. package/src/popup-surface.tsx +31 -0
  85. package/src/popup.ts +569 -0
  86. package/src/position.ts +523 -0
  87. package/src/progress.tsx +70 -0
  88. package/src/radio-group.tsx +186 -0
  89. package/src/resizable.tsx +310 -0
  90. package/src/segments.ts +922 -0
  91. package/src/select.tsx +1501 -0
  92. package/src/sidebar.tsx +683 -0
  93. package/src/slider.tsx +424 -0
  94. package/src/switch.tsx +104 -0
  95. package/src/tabs.tsx +314 -0
  96. package/src/toast.tsx +923 -0
  97. package/src/toggle-group.tsx +249 -0
  98. package/src/toggle.tsx +91 -0
  99. package/src/toolbar.tsx +212 -0
  100. package/src/tooltip.tsx +359 -0
  101. package/src/utils.ts +204 -0
  102. package/src/virtual-list.tsx +205 -0
  103. package/src/virtual.ts +385 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2026, Cristian Falcone
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # ajo-ui
2
+
3
+ Unstyled, accessible component families for Ajo applications.
4
+
5
+ Components provide semantic markup, ARIA relationships, keyboard behavior,
6
+ stable styling hooks, and controlled or uncontrolled state. Use them directly
7
+ to build an application UI or a reusable theme.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add ajo ajo-ui
13
+ ```
14
+
15
+ `ajo-ui` requires `ajo ^0.1.35`.
16
+
17
+ ## Usage
18
+
19
+ Import a component family from its subpath:
20
+
21
+ ```tsx
22
+ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'ajo-ui/accordion'
23
+ import { Popover, PopoverContent, PopoverTrigger } from 'ajo-ui/popover'
24
+ ```
25
+
26
+ The package root exports every component family:
27
+
28
+ ```tsx
29
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ajo-ui'
30
+ ```
31
+
32
+ ```tsx
33
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ajo-ui/tabs'
34
+
35
+ export default () => (
36
+ <Tabs defaultValue="overview">
37
+ <TabsList aria-label="Project">
38
+ <TabsTrigger value="overview">Overview</TabsTrigger>
39
+ <TabsTrigger value="activity">Activity</TabsTrigger>
40
+ </TabsList>
41
+
42
+ <TabsContent value="overview">Project overview</TabsContent>
43
+ <TabsContent value="activity">Recent activity</TabsContent>
44
+ </Tabs>
45
+ )
46
+ ```
47
+
48
+ ## Components
49
+
50
+ | Group | Family subpaths |
51
+ |---|---|
52
+ | Foundations | `direction`, `field`, `input-group` |
53
+ | Disclosure and layout | `accordion`, `collapsible`, `dialog`, `drawer`, `resizable`, `sidebar` |
54
+ | Navigation and menus | `command`, `context-menu`, `menu`, `menubar`, `navigation-menu`, `tabs`, `toolbar` |
55
+ | Inputs and selection | `calendar`, `checkbox`, `checkbox-group`, `input-date`, `input-otp`, `radio-group`, `select`, `slider`, `switch`, `toggle`, `toggle-group` |
56
+ | Overlays and feedback | `popover`, `progress`, `toast`, `tooltip` |
57
+ | Data and display | `avatar`, `carousel`, `chart`, `data-table`, `message-scroller`, `virtual-list` |
58
+
59
+ ## Component Utilities
60
+
61
+ `ajo-ui/utils` provides helpers and types for custom components, themes, and
62
+ adapters:
63
+
64
+ ```tsx
65
+ import {
66
+ ariaChecked,
67
+ bool,
68
+ clx,
69
+ flag,
70
+ popupStyle,
71
+ stlx,
72
+ text,
73
+ triggerAttrs,
74
+ withSlot,
75
+ } from 'ajo-ui/utils'
76
+ import type {
77
+ CheckedState,
78
+ FixedArgs,
79
+ OmitArg,
80
+ PopupPlacement,
81
+ PopupPosition,
82
+ StyleInput,
83
+ StyleObject,
84
+ StyleValue,
85
+ } from 'ajo-ui/utils'
86
+ ```
87
+
88
+ | Area | Exports |
89
+ |---|---|
90
+ | Popup composition | `PopupPlacement`, `PopupPosition`, `triggerAttrs`, `popupStyle` |
91
+ | Component adapters | `OmitArg`, `FixedArgs`, `withSlot` |
92
+ | Checked state | `CheckedState`, `ariaChecked`, `syncCheckedState` |
93
+ | Values and filtering | `bool`, `flag`, `text`, `strings`, `matchesTokens`, `defaultResultsLabel`, `resolveFilter`, `toNumber`, `emptyChildren` |
94
+ | Classes and styles | `clx`, `stlx`, `StyleValue`, `StyleObject`, `StyleInput` |
95
+
96
+ `OmitArg` removes named properties while preserving Ajo's open argument
97
+ index. `FixedArgs` marks properties supplied by an adapter.
98
+
99
+ ## Styling
100
+
101
+ Components render semantic elements, ARIA attributes, `data-slot` markers, and
102
+ state attributes. Apply visual styles through `class`, family class maps, or
103
+ state-aware class callbacks such as `dayClassName`.
104
+
105
+ Boolean state attributes use `data-x="true"` when active. Common
106
+ `data-state` values include `open`, `closed`, `checked`, `unchecked`,
107
+ `active`, `inactive`, `on`, and `off`.
108
+
109
+ ## State
110
+
111
+ Controlled and uncontrolled families use matching prop groups:
112
+
113
+ - `value`, `defaultValue`, `onValueChange(value, event)`
114
+ - `open`, `defaultOpen`, `onOpenChange(open, event)`
115
+ - `checked`, `defaultChecked`, `onCheckedChange(checked, event)`
116
+
117
+ ## Localization and Direction
118
+
119
+ User-visible and assistive-technology strings have English defaults and
120
+ component args for replacement.
121
+
122
+ `DirectionProvider` supplies the default text direction. Components with
123
+ horizontal keyboard navigation also accept a `dir` override.
124
+
125
+ Component-family subpaths and root imports are side-effect-free and
126
+ tree-shakeable.
@@ -0,0 +1,130 @@
1
+ import { strings } from "./utils.js";
2
+ import { Collapsible, CollapsibleContent, CollapsibleContext, CollapsibleTrigger } from "./collapsible.js";
3
+ import { controlled, listen, roving, statefulRootAttrs } from "ajo-cloves";
4
+ import { context } from "ajo/context";
5
+ import { Fragment, jsx } from "ajo/jsx-runtime";
6
+ //#region packages/ajo-ui/src/accordion.tsx
7
+ var AccordionContext = context(null);
8
+ var sameArray = (first, second) => first.length === second.length && first.every((value, index) => value === second[index]);
9
+ var AccordionRoot = function* ({ defaultValue, type, value }) {
10
+ let collapsible = false;
11
+ let disabled = false;
12
+ let onValueChange;
13
+ let mode = type;
14
+ const multipleState = controlled(this, {
15
+ fallback: type === "multiple" ? strings(value ?? defaultValue) : [],
16
+ onChange: (next, event) => {
17
+ onValueChange?.(next, event);
18
+ }
19
+ });
20
+ const singleState = controlled(this, {
21
+ fallback: type === "single" ? String(value ?? defaultValue ?? "") : "",
22
+ onChange: (next, event) => {
23
+ onValueChange?.(next, event);
24
+ }
25
+ });
26
+ let multiple = multipleState.value;
27
+ let single = singleState.value;
28
+ const isOpen = (itemValue) => mode === "multiple" ? multiple.includes(itemValue) : single === itemValue;
29
+ const setSingle = (next, event) => {
30
+ if (next === single) return;
31
+ singleState.set(next, event);
32
+ };
33
+ const setMultiple = (next, event) => {
34
+ if (sameArray(next, multiple)) return;
35
+ multipleState.set(next, event);
36
+ };
37
+ const toggle = (itemValue, event) => {
38
+ if (disabled) return;
39
+ if (mode === "multiple") {
40
+ setMultiple(multiple.includes(itemValue) ? multiple.filter((value) => value !== itemValue) : [...multiple, itemValue], event);
41
+ return;
42
+ }
43
+ if (single === itemValue) {
44
+ if (collapsible) setSingle("", event);
45
+ return;
46
+ }
47
+ setSingle(itemValue, event);
48
+ };
49
+ const nav = roving(this, {
50
+ items: () => Array.from(this.querySelectorAll("[data-slot=\"accordion-trigger\"]")).filter((trigger) => trigger.getAttribute("aria-disabled") !== "true" && trigger.offsetParent !== null),
51
+ onMove: (target) => target.focus()
52
+ });
53
+ listen(this, "keydown", (event) => {
54
+ if (!event.target?.matches("[data-slot=\"accordion-trigger\"]")) return;
55
+ nav.handle(event);
56
+ });
57
+ for (const args of this) {
58
+ mode = args.type ?? "single";
59
+ collapsible = mode === "single" && Boolean(args.collapsible);
60
+ disabled = Boolean(args.disabled);
61
+ onValueChange = args.onValueChange;
62
+ multipleState.sync(mode === "multiple" && args.value != null ? strings(args.value) : void 0);
63
+ singleState.sync(mode === "single" && args.value != null ? String(args.value ?? "") : void 0);
64
+ multiple = mode === "multiple" ? multipleState.value : [];
65
+ single = mode === "single" ? singleState.value : "";
66
+ AccordionContext({
67
+ collapsible,
68
+ disabled,
69
+ isOpen,
70
+ toggle,
71
+ type: mode
72
+ });
73
+ yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
74
+ }
75
+ };
76
+ /** Unstyled root provider for accordion state. */
77
+ var Accordion = ({ children, collapsible, defaultValue, disabled, onValueChange, type = "single", value, ...attrs }) => /* @__PURE__ */ jsx(AccordionRoot, {
78
+ ...statefulRootAttrs(attrs),
79
+ collapsible: collapsible === true,
80
+ defaultValue,
81
+ disabled: Boolean(disabled),
82
+ onValueChange,
83
+ type,
84
+ value,
85
+ "attr:data-slot": "accordion",
86
+ children
87
+ });
88
+ /** Unstyled accordion section: a group-controlled collapsible details element. */
89
+ var AccordionItem = ({ children, disabled, open: _open, value, ...attrs }) => {
90
+ const accordion = AccordionContext();
91
+ const itemValue = String(value);
92
+ const opened = Boolean(accordion?.isOpen(itemValue));
93
+ const disabledFlag = Boolean(disabled ?? accordion?.disabled);
94
+ return /* @__PURE__ */ jsx(Collapsible, {
95
+ "data-orientation": "vertical",
96
+ "data-slot": "accordion-item",
97
+ "data-value": value,
98
+ ...attrs,
99
+ disabled: disabledFlag,
100
+ onOpenChange: (_next, event) => accordion?.toggle(itemValue, event),
101
+ open: opened,
102
+ children
103
+ });
104
+ };
105
+ /** Unstyled accordion heading trigger: a collapsible summary locked by group rules. */
106
+ var AccordionTrigger = ({ children, ...attrs }) => {
107
+ const accordion = AccordionContext();
108
+ const item = CollapsibleContext();
109
+ const locked = Boolean(item?.open && accordion?.type === "single" && !accordion.collapsible);
110
+ return /* @__PURE__ */ jsx(CollapsibleTrigger, {
111
+ "data-orientation": "vertical",
112
+ "data-slot": "accordion-trigger",
113
+ ...attrs,
114
+ locked,
115
+ children
116
+ });
117
+ };
118
+ /** Unstyled accordion panel content. */
119
+ var AccordionContent = ({ children, innerClass, ...attrs }) => /* @__PURE__ */ jsx(CollapsibleContent, {
120
+ "data-orientation": "vertical",
121
+ "data-slot": "accordion-content",
122
+ role: "region",
123
+ ...attrs,
124
+ children: /* @__PURE__ */ jsx("div", {
125
+ class: innerClass,
126
+ children
127
+ })
128
+ });
129
+ //#endregion
130
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
package/dist/avatar.js ADDED
@@ -0,0 +1,54 @@
1
+ import { callHandler } from "ajo-cloves";
2
+ import { jsx } from "ajo/jsx-runtime";
3
+ //#region packages/ajo-ui/src/avatar.tsx
4
+ /** Unstyled avatar root that exposes image, fallback, and badge slots. */
5
+ var Avatar = ({ children, size = "default", ...attrs }) => /* @__PURE__ */ jsx("div", {
6
+ ...attrs,
7
+ "data-size": size,
8
+ "data-slot": "avatar",
9
+ children
10
+ });
11
+ /** Unstyled avatar image that hides itself after load errors. */
12
+ var AvatarImage = ({ alt = "", decoding = "async", loading = "lazy", "set:onerror": onerror, "set:onload": onload, ...attrs }) => /* @__PURE__ */ jsx("img", {
13
+ ...attrs,
14
+ alt,
15
+ "data-slot": "avatar-image",
16
+ decoding,
17
+ loading,
18
+ "set:onerror": (event) => {
19
+ if (event instanceof Event) {
20
+ event.currentTarget.hidden = true;
21
+ callHandler(onerror, event);
22
+ }
23
+ },
24
+ "set:onload": (event) => {
25
+ event.currentTarget.hidden = false;
26
+ callHandler(onload, event);
27
+ }
28
+ });
29
+ /** Unstyled fallback content for an Avatar. */
30
+ var AvatarFallback = ({ children, ...attrs }) => /* @__PURE__ */ jsx("span", {
31
+ ...attrs,
32
+ "data-slot": "avatar-fallback",
33
+ children
34
+ });
35
+ /** Unstyled badge anchored to an Avatar. */
36
+ var AvatarBadge = ({ children, ...attrs }) => /* @__PURE__ */ jsx("span", {
37
+ ...attrs,
38
+ "data-slot": "avatar-badge",
39
+ children
40
+ });
41
+ /** Unstyled container for a group of avatars. */
42
+ var AvatarGroup = ({ children, ...attrs }) => /* @__PURE__ */ jsx("div", {
43
+ ...attrs,
44
+ "data-slot": "avatar-group",
45
+ children
46
+ });
47
+ /** Unstyled count for avatars omitted from a visible group. */
48
+ var AvatarGroupCount = ({ children, ...attrs }) => /* @__PURE__ */ jsx("div", {
49
+ ...attrs,
50
+ "data-slot": "avatar-group-count",
51
+ children
52
+ });
53
+ //#endregion
54
+ export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage };
@@ -0,0 +1,2 @@
1
+ import { n as CalendarDayButton, t as Calendar } from "./chunks/calendar-q8jZ8Cxr.js";
2
+ export { Calendar, CalendarDayButton };
@@ -0,0 +1,239 @@
1
+ import { callHandler, clamp, listen, resize, scrolling, statefulRootAttrs } from "ajo-cloves";
2
+ import { context } from "ajo/context";
3
+ import { Fragment, jsx } from "ajo/jsx-runtime";
4
+ //#region packages/ajo-ui/src/carousel.tsx
5
+ /** Read the observable state and controls inherited from the nearest Carousel. */
6
+ var CarouselContext = context(null);
7
+ var CarouselPartsContext = context(null);
8
+ var carousel = () => {
9
+ const value = CarouselContext();
10
+ if (!value) throw new Error("Carousel parts must be used within a <Carousel />");
11
+ return value;
12
+ };
13
+ var carouselParts = () => {
14
+ const value = CarouselPartsContext();
15
+ if (!value) throw new Error("Carousel parts must be used within a <Carousel />");
16
+ return value;
17
+ };
18
+ var axis = (orientation) => orientation === "horizontal" ? "x" : "y";
19
+ var alignBlock = (orientation) => orientation === "vertical" ? "start" : "nearest";
20
+ var alignInline = (orientation) => orientation === "horizontal" ? "start" : "nearest";
21
+ var itemPosition = (item, orientation) => orientation === "horizontal" ? item.offsetLeft : item.offsetTop;
22
+ var viewportPosition = (viewport, orientation) => orientation === "horizontal" ? viewport.scrollLeft : viewport.scrollTop;
23
+ var viewportSize = (viewport, orientation) => orientation === "horizontal" ? viewport.clientWidth : viewport.clientHeight;
24
+ var itemSize = (item, orientation) => orientation === "horizontal" ? item.offsetWidth : item.offsetHeight;
25
+ var CarouselRoot = function* () {
26
+ let viewport = null;
27
+ let orientation = "horizontal";
28
+ let loop = false;
29
+ let selected = 0;
30
+ let count = 0;
31
+ let canScrollPrev = false;
32
+ let canScrollNext = false;
33
+ let apiReceiver;
34
+ const listeners = /* @__PURE__ */ new Map();
35
+ const items = () => viewport ? Array.from(viewport.querySelectorAll("[data-slot=\"carousel-item\"]")) : [];
36
+ const emit = (event) => {
37
+ for (const listener of listeners.get(event) ?? []) listener();
38
+ };
39
+ const syncScrollability = () => {
40
+ canScrollPrev = Boolean(count) && (loop || selected > 0);
41
+ canScrollNext = Boolean(count) && (loop || selected < count - 1);
42
+ };
43
+ const sync = () => {
44
+ const nodes = items();
45
+ const previousSelected = selected;
46
+ const previousCount = count;
47
+ const previousCanScrollPrev = canScrollPrev;
48
+ const previousCanScrollNext = canScrollNext;
49
+ count = nodes.length;
50
+ if (!viewport || !count) selected = 0;
51
+ else {
52
+ const center = viewportPosition(viewport, orientation) + viewportSize(viewport, orientation) / 2;
53
+ let nextSelected = 0;
54
+ let distance = Number.POSITIVE_INFINITY;
55
+ nodes.forEach((item, index) => {
56
+ const next = Math.abs(itemPosition(item, orientation) + itemSize(item, orientation) / 2 - center);
57
+ if (next < distance) {
58
+ distance = next;
59
+ nextSelected = index;
60
+ }
61
+ });
62
+ selected = nextSelected;
63
+ }
64
+ syncScrollability();
65
+ const changed = previousSelected !== selected || previousCount !== count || previousCanScrollPrev !== canScrollPrev || previousCanScrollNext !== canScrollNext;
66
+ if (previousCount !== count) emit("reInit");
67
+ if (previousSelected !== selected) emit("select");
68
+ return changed;
69
+ };
70
+ const invalidate = () => {
71
+ if (sync()) this.next();
72
+ };
73
+ const scroll = scrolling(this, {
74
+ target: () => viewport,
75
+ onScroll: invalidate
76
+ });
77
+ const size = resize(this, {
78
+ target: () => viewport,
79
+ onResize: invalidate
80
+ });
81
+ const scrollTo = (index) => {
82
+ const nodes = items();
83
+ if (!nodes.length) return;
84
+ nodes[clamp(index, 0, nodes.length - 1)]?.scrollIntoView({
85
+ behavior: "smooth",
86
+ block: alignBlock(orientation),
87
+ inline: alignInline(orientation)
88
+ });
89
+ };
90
+ const scrollPrev = () => {
91
+ if (!count) return;
92
+ scrollTo(selected <= 0 && loop ? count - 1 : selected - 1);
93
+ };
94
+ const scrollNext = () => {
95
+ if (!count) return;
96
+ scrollTo(selected >= count - 1 && loop ? 0 : selected + 1);
97
+ };
98
+ const api = {
99
+ canScrollNext: () => canScrollNext,
100
+ canScrollPrev: () => canScrollPrev,
101
+ off: (event, listener) => listeners.get(event)?.delete(listener),
102
+ on: (event, listener) => {
103
+ const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
104
+ set.add(listener);
105
+ listeners.set(event, set);
106
+ },
107
+ reInit: () => {
108
+ this.next(sync);
109
+ },
110
+ scrollNext,
111
+ scrollPrev,
112
+ scrollSnapList: () => items().map((_, index) => index),
113
+ scrollTo,
114
+ selectedScrollSnap: () => selected
115
+ };
116
+ const setViewport = (element) => {
117
+ if (viewport === element) return;
118
+ viewport = element;
119
+ scroll.sync();
120
+ size.sync();
121
+ this.next(sync);
122
+ };
123
+ const parts = { setViewport };
124
+ const keydown = (event) => {
125
+ if (orientation === "horizontal") {
126
+ if (event.key === "ArrowLeft") {
127
+ event.preventDefault();
128
+ scrollPrev();
129
+ } else if (event.key === "ArrowRight") {
130
+ event.preventDefault();
131
+ scrollNext();
132
+ }
133
+ } else if (event.key === "ArrowUp") {
134
+ event.preventDefault();
135
+ scrollPrev();
136
+ } else if (event.key === "ArrowDown") {
137
+ event.preventDefault();
138
+ scrollNext();
139
+ }
140
+ };
141
+ listen(this, "keydown", keydown);
142
+ for (const { children, opts, orientation: nextOrientation, setApi } of this) {
143
+ orientation = nextOrientation ?? (opts?.axis === "y" ? "vertical" : "horizontal");
144
+ loop = Boolean(opts?.loop);
145
+ syncScrollability();
146
+ if (setApi && setApi !== apiReceiver) {
147
+ apiReceiver = setApi;
148
+ setApi(api);
149
+ }
150
+ scroll.sync();
151
+ size.sync();
152
+ CarouselContext({
153
+ canScrollNext,
154
+ canScrollPrev,
155
+ count,
156
+ orientation,
157
+ scrollNext,
158
+ scrollPrev,
159
+ selected
160
+ });
161
+ CarouselPartsContext(parts);
162
+ yield /* @__PURE__ */ jsx(Fragment, { children });
163
+ }
164
+ };
165
+ /** Unstyled native scroll-snap carousel root. */
166
+ var Carousel = ({ children, dir, opts, orientation, role = "region", setApi, ...attrs }) => /* @__PURE__ */ jsx(CarouselRoot, {
167
+ ...statefulRootAttrs(attrs),
168
+ opts,
169
+ orientation,
170
+ setApi,
171
+ "attr:aria-roledescription": "carousel",
172
+ "attr:data-axis": axis(orientation ?? (opts?.axis === "y" ? "vertical" : "horizontal")),
173
+ "attr:data-slot": "carousel",
174
+ "attr:dir": opts?.direction ?? dir,
175
+ "attr:role": role,
176
+ children
177
+ });
178
+ /** Unstyled scroll viewport and track for carousel slides. */
179
+ var CarouselContent = ({ children, viewportClass, ...attrs }) => {
180
+ const state = carousel();
181
+ const parts = carouselParts();
182
+ return /* @__PURE__ */ jsx("div", {
183
+ class: viewportClass,
184
+ "data-slot": "carousel-content",
185
+ children: /* @__PURE__ */ jsx("div", {
186
+ ...attrs,
187
+ "data-axis": axis(state.orientation),
188
+ "data-slot": "carousel-track",
189
+ ref: parts.setViewport,
190
+ children
191
+ })
192
+ });
193
+ };
194
+ /** Unstyled carousel slide item. */
195
+ var CarouselItem = ({ children, role = "group", ...attrs }) => /* @__PURE__ */ jsx("div", {
196
+ ...attrs,
197
+ "aria-roledescription": "slide",
198
+ "data-slot": "carousel-item",
199
+ role,
200
+ children
201
+ });
202
+ /** Unstyled previous slide button. */
203
+ var CarouselPrevious = ({ children, "aria-label": label = "Previous slide", disabled, type = "button", "set:onclick": onClick, ...attrs }) => {
204
+ const { canScrollPrev, scrollPrev } = carousel();
205
+ const click = (event) => {
206
+ callHandler(onClick, event);
207
+ if (event.defaultPrevented || disabled || !canScrollPrev) return;
208
+ scrollPrev();
209
+ };
210
+ return /* @__PURE__ */ jsx("button", {
211
+ ...attrs,
212
+ "aria-label": label,
213
+ "data-slot": "carousel-previous",
214
+ disabled: disabled || !canScrollPrev,
215
+ "set:onclick": click,
216
+ type,
217
+ children
218
+ });
219
+ };
220
+ /** Unstyled next slide button. */
221
+ var CarouselNext = ({ children, "aria-label": label = "Next slide", disabled, type = "button", "set:onclick": onClick, ...attrs }) => {
222
+ const { canScrollNext, scrollNext } = carousel();
223
+ const click = (event) => {
224
+ callHandler(onClick, event);
225
+ if (event.defaultPrevented || disabled || !canScrollNext) return;
226
+ scrollNext();
227
+ };
228
+ return /* @__PURE__ */ jsx("button", {
229
+ ...attrs,
230
+ "aria-label": label,
231
+ "data-slot": "carousel-next",
232
+ disabled: disabled || !canScrollNext,
233
+ "set:onclick": click,
234
+ type,
235
+ children
236
+ });
237
+ };
238
+ //#endregion
239
+ export { Carousel, CarouselContent, CarouselContext, CarouselItem, CarouselNext, CarouselPrevious };