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
@@ -0,0 +1,343 @@
1
+ import { Drawer, DrawerContent } from "./drawer.js";
2
+ import { browser, callHandler, controlled, dom, hotkey, media, statefulRootAttrs } from "ajo-cloves";
3
+ import { context } from "ajo/context";
4
+ import { Fragment, jsx, jsxs } from "ajo/jsx-runtime";
5
+ //#region packages/ajo-ui/src/sidebar.tsx
6
+ /** Responsive state and controls provided by the nearest SidebarProvider. */
7
+ var SidebarContext = context(null);
8
+ var cookie = "sidebar_state";
9
+ var cookieMaxAge = 3600 * 24 * 7;
10
+ var defaultMobileQuery = "(max-width: 767px)";
11
+ var defaultShortcut = "mod+b";
12
+ var vars = (style) => [
13
+ "--sidebar-width:16rem",
14
+ "--sidebar-width-mobile:18rem",
15
+ "--sidebar-width-icon:3rem",
16
+ style
17
+ ].filter(Boolean).join(";");
18
+ var writeCookie = (open) => {
19
+ if (!browser()) return;
20
+ document.cookie = `${cookie}=${open}; path=/; max-age=${cookieMaxAge}`;
21
+ };
22
+ var sidebar = () => {
23
+ const value = SidebarContext();
24
+ if (!value) throw new Error("Sidebar controls must be used within a <SidebarProvider />.");
25
+ return value;
26
+ };
27
+ var SidebarProviderRoot = function* ({ defaultOpen = true, open }) {
28
+ let mobileQuery = defaultMobileQuery;
29
+ let onOpenChange;
30
+ let persist;
31
+ let shortcut = defaultShortcut;
32
+ const mobile = media(this, { query: () => mobileQuery });
33
+ const state = controlled(this, {
34
+ fallback: Boolean(open ?? defaultOpen),
35
+ onChange: (next, event) => {
36
+ onOpenChange?.(next, event);
37
+ if (persist !== false) (persist ?? writeCookie)(next);
38
+ }
39
+ });
40
+ const mobileState = controlled(this, {
41
+ fallback: false,
42
+ onChange: (next, event) => onOpenChange?.(next, event)
43
+ });
44
+ const setOpen = (next, event) => state.set(next, event);
45
+ const setOpenMobile = (next, event) => mobileState.set(next, event);
46
+ const toggleSidebar = (event) => mobile.matches ? setOpenMobile(!mobileState.value, event) : setOpen(!state.value, event);
47
+ hotkey(this, {
48
+ keys: () => shortcut || "",
49
+ active: () => shortcut !== false,
50
+ onPress: (event) => toggleSidebar(event)
51
+ });
52
+ for (const args of this) {
53
+ mobileQuery = args.mobileQuery ?? defaultMobileQuery;
54
+ onOpenChange = args.onOpenChange;
55
+ persist = args.persist;
56
+ shortcut = args.shortcut ?? defaultShortcut;
57
+ mobile.sync();
58
+ if (dom(this)) this.setAttribute("data-mobile", String(mobile.matches));
59
+ if (!mobile.matches && mobileState.value) queueMicrotask(() => {
60
+ if (!this.signal.aborted && !mobile.matches && mobileState.value) mobileState.set(false);
61
+ });
62
+ const opened = state.sync(args.open != null ? Boolean(args.open) : void 0);
63
+ SidebarContext({
64
+ isMobile: mobile.matches,
65
+ open: opened,
66
+ openMobile: mobileState.value,
67
+ setOpen,
68
+ setOpenMobile,
69
+ state: opened ? "expanded" : "collapsed",
70
+ toggleSidebar
71
+ });
72
+ yield /* @__PURE__ */ jsx(Fragment, { children: args.children });
73
+ }
74
+ };
75
+ /** Unstyled state provider for the sidebar component family. */
76
+ var SidebarProvider = ({ children, class: classes, defaultOpen, mobileQuery, onOpenChange, open, persist, shortcut, style, ...attrs }) => /* @__PURE__ */ jsx(SidebarProviderRoot, {
77
+ ...statefulRootAttrs(attrs),
78
+ defaultOpen,
79
+ mobileQuery,
80
+ onOpenChange,
81
+ open,
82
+ persist,
83
+ shortcut,
84
+ "attr:class": classes,
85
+ "attr:data-slot": "sidebar-wrapper",
86
+ "attr:style": vars(style),
87
+ children
88
+ });
89
+ /** Unstyled main sidebar panel. */
90
+ var Sidebar = ({ children, class: classes, collapsible = "offcanvas", mobileClass, mobileContentClass, side = "left", variant = "sidebar", ...attrs }) => {
91
+ const ctx = SidebarContext();
92
+ const state = collapsible === "none" ? "expanded" : ctx?.state ?? "expanded";
93
+ const collapsed = state === "collapsed" ? collapsible : "";
94
+ if (ctx?.isMobile && collapsible !== "none") return /* @__PURE__ */ jsx(Drawer, {
95
+ onOpenChange: (next, event) => ctx.setOpenMobile(next, event),
96
+ open: ctx.openMobile,
97
+ side,
98
+ style: "position:fixed",
99
+ children: /* @__PURE__ */ jsx(DrawerContent, {
100
+ ...attrs,
101
+ "aria-label": attrs["aria-label"] ?? "Sidebar",
102
+ class: mobileClass ?? classes,
103
+ "data-mobile": "true",
104
+ "data-variant": variant,
105
+ showCloseButton: false,
106
+ ref: (element) => element?.setAttribute("data-slot", "sidebar"),
107
+ children: /* @__PURE__ */ jsx("div", {
108
+ class: mobileContentClass,
109
+ children
110
+ })
111
+ })
112
+ });
113
+ return /* @__PURE__ */ jsx("aside", {
114
+ ...attrs,
115
+ "aria-label": attrs["aria-label"] ?? "Sidebar",
116
+ class: classes,
117
+ "data-collapsible": collapsed || void 0,
118
+ "data-side": side,
119
+ "data-slot": "sidebar",
120
+ "data-state": state,
121
+ "data-variant": variant,
122
+ children
123
+ });
124
+ };
125
+ /** Toggles the nearest SidebarProvider unless a caller-supplied click handler prevented default. */
126
+ var toggleClick = (ctx, onClick) => (event) => {
127
+ callHandler(onClick, event);
128
+ if (!event.defaultPrevented) ctx.toggleSidebar(event);
129
+ };
130
+ /** Unstyled button that toggles the current SidebarProvider. */
131
+ var SidebarTrigger = ({ "aria-label": label = "Toggle Sidebar", children, class: classes, type = "button", "set:onclick": onClick, ...attrs }) => {
132
+ const ctx = sidebar();
133
+ return /* @__PURE__ */ jsx("button", {
134
+ ...attrs,
135
+ "aria-label": label,
136
+ class: classes,
137
+ "data-sidebar": "trigger",
138
+ "data-slot": "sidebar-trigger",
139
+ type,
140
+ "set:onclick": toggleClick(ctx, onClick),
141
+ children
142
+ });
143
+ };
144
+ /** Unstyled rail hit area used to collapse or expand a sidebar. */
145
+ var SidebarRail = ({ "aria-label": label = "Toggle Sidebar", class: classes, type = "button", "set:onclick": onClick, ...attrs }) => {
146
+ const ctx = sidebar();
147
+ return /* @__PURE__ */ jsx("button", {
148
+ ...attrs,
149
+ "aria-label": label,
150
+ class: classes,
151
+ "data-sidebar": "rail",
152
+ "data-slot": "sidebar-rail",
153
+ tabindex: "-1",
154
+ title: label,
155
+ type,
156
+ "set:onclick": toggleClick(ctx, onClick)
157
+ });
158
+ };
159
+ /** Unstyled main content wrapper used with inset sidebars. */
160
+ var SidebarInset = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("main", {
161
+ ...attrs,
162
+ class: classes,
163
+ "data-slot": "sidebar-inset",
164
+ children
165
+ });
166
+ /** Unstyled input carrying the sidebar slot and data markers. */
167
+ var SidebarInput = ({ class: classes, ...attrs }) => /* @__PURE__ */ jsx("input", {
168
+ ...attrs,
169
+ class: classes,
170
+ "data-sidebar": "input",
171
+ "data-slot": "sidebar-input"
172
+ });
173
+ /** Unstyled container for content at the top of a sidebar. */
174
+ var SidebarHeader = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
175
+ ...attrs,
176
+ class: classes,
177
+ "data-sidebar": "header",
178
+ "data-slot": "sidebar-header",
179
+ children
180
+ });
181
+ /** Unstyled container for content at the bottom of a sidebar. */
182
+ var SidebarFooter = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
183
+ ...attrs,
184
+ class: classes,
185
+ "data-sidebar": "footer",
186
+ "data-slot": "sidebar-footer",
187
+ children
188
+ });
189
+ /** Unstyled decorative separator between sidebar sections. */
190
+ var SidebarSeparator = ({ class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
191
+ ...attrs,
192
+ "aria-hidden": "true",
193
+ class: classes,
194
+ "data-sidebar": "separator",
195
+ "data-slot": "sidebar-separator",
196
+ role: "none"
197
+ });
198
+ /** Unstyled container for the sidebar's primary content. */
199
+ var SidebarContent = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
200
+ ...attrs,
201
+ class: classes,
202
+ "data-sidebar": "content",
203
+ "data-slot": "sidebar-content",
204
+ children
205
+ });
206
+ /** Unstyled wrapper for a related group of sidebar controls. */
207
+ var SidebarGroup = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
208
+ ...attrs,
209
+ class: classes,
210
+ "data-sidebar": "group",
211
+ "data-slot": "sidebar-group",
212
+ children
213
+ });
214
+ /** Unstyled label for a sidebar group. */
215
+ var SidebarGroupLabel = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
216
+ ...attrs,
217
+ class: classes,
218
+ "data-sidebar": "group-label",
219
+ "data-slot": "sidebar-group-label",
220
+ children
221
+ });
222
+ /** Unstyled action button associated with a sidebar group. */
223
+ var SidebarGroupAction = ({ children, class: classes, type = "button", ...attrs }) => /* @__PURE__ */ jsx("button", {
224
+ ...attrs,
225
+ class: classes,
226
+ "data-sidebar": "group-action",
227
+ "data-slot": "sidebar-group-action",
228
+ type,
229
+ children
230
+ });
231
+ /** Unstyled content container inside a sidebar group. */
232
+ var SidebarGroupContent = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
233
+ ...attrs,
234
+ class: classes,
235
+ "data-sidebar": "group-content",
236
+ "data-slot": "sidebar-group-content",
237
+ children
238
+ });
239
+ /** Unstyled list for primary sidebar navigation items. */
240
+ var SidebarMenu = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("ul", {
241
+ ...attrs,
242
+ class: classes,
243
+ "data-sidebar": "menu",
244
+ "data-slot": "sidebar-menu",
245
+ children
246
+ });
247
+ /** Unstyled item in the primary sidebar menu. */
248
+ var SidebarMenuItem = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("li", {
249
+ ...attrs,
250
+ class: classes,
251
+ "data-sidebar": "menu-item",
252
+ "data-slot": "sidebar-menu-item",
253
+ children
254
+ });
255
+ /** Unstyled primary sidebar menu control rendered as a button or anchor. */
256
+ var SidebarMenuButton = ({ as = "button", children, class: classes, isActive, size = "default", tooltip, ...attrs }) => {
257
+ const ctx = SidebarContext();
258
+ const title = tooltip && ctx?.state === "collapsed" ? tooltip : attrs.title;
259
+ if (as === "a") return /* @__PURE__ */ jsx("a", {
260
+ ...attrs,
261
+ "aria-current": isActive ? "page" : void 0,
262
+ class: classes,
263
+ "data-active": isActive ? "true" : void 0,
264
+ "data-sidebar": "menu-button",
265
+ "data-size": size,
266
+ "data-slot": "sidebar-menu-button",
267
+ title,
268
+ children
269
+ });
270
+ const { type = "button", ...button } = attrs;
271
+ return /* @__PURE__ */ jsx("button", {
272
+ ...button,
273
+ class: classes,
274
+ "data-active": isActive ? "true" : void 0,
275
+ "data-sidebar": "menu-button",
276
+ "data-size": size,
277
+ "data-slot": "sidebar-menu-button",
278
+ title,
279
+ type,
280
+ children
281
+ });
282
+ };
283
+ /** Unstyled secondary action attached to a sidebar menu item. */
284
+ var SidebarMenuAction = ({ children, class: classes, type = "button", ...attrs }) => /* @__PURE__ */ jsx("button", {
285
+ ...attrs,
286
+ class: classes,
287
+ "data-sidebar": "menu-action",
288
+ "data-slot": "sidebar-menu-action",
289
+ type,
290
+ children
291
+ });
292
+ /** Unstyled metadata badge displayed beside a sidebar menu item. */
293
+ var SidebarMenuBadge = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("div", {
294
+ ...attrs,
295
+ class: classes,
296
+ "data-sidebar": "menu-badge",
297
+ "data-slot": "sidebar-menu-badge",
298
+ children
299
+ });
300
+ /** Unstyled loading placeholder for a sidebar menu item. */
301
+ var SidebarMenuSkeleton = ({ class: classes, iconClass, showIcon, textClass, width = "70%", ...attrs }) => /* @__PURE__ */ jsxs("div", {
302
+ ...attrs,
303
+ class: classes,
304
+ "data-sidebar": "menu-skeleton",
305
+ "data-slot": "sidebar-menu-skeleton",
306
+ children: [showIcon ? /* @__PURE__ */ jsx("div", {
307
+ class: iconClass,
308
+ "data-sidebar": "menu-skeleton-icon"
309
+ }) : null, /* @__PURE__ */ jsx("div", {
310
+ class: textClass,
311
+ "data-sidebar": "menu-skeleton-text",
312
+ style: `max-width:${width}`
313
+ })]
314
+ });
315
+ /** Unstyled nested list within the sidebar menu. */
316
+ var SidebarMenuSub = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("ul", {
317
+ ...attrs,
318
+ class: classes,
319
+ "data-sidebar": "menu-sub",
320
+ "data-slot": "sidebar-menu-sub",
321
+ children
322
+ });
323
+ /** Unstyled item in a nested sidebar menu. */
324
+ var SidebarMenuSubItem = ({ children, class: classes, ...attrs }) => /* @__PURE__ */ jsx("li", {
325
+ ...attrs,
326
+ class: classes,
327
+ "data-sidebar": "menu-sub-item",
328
+ "data-slot": "sidebar-menu-sub-item",
329
+ children
330
+ });
331
+ /** Unstyled anchor for a nested sidebar menu item. */
332
+ var SidebarMenuSubButton = ({ children, class: classes, isActive, size = "md", ...attrs }) => /* @__PURE__ */ jsx("a", {
333
+ ...attrs,
334
+ "aria-current": isActive ? "page" : void 0,
335
+ class: classes,
336
+ "data-active": isActive ? "true" : void 0,
337
+ "data-sidebar": "menu-sub-button",
338
+ "data-size": size,
339
+ "data-slot": "sidebar-menu-sub-button",
340
+ children
341
+ });
342
+ //#endregion
343
+ export { Sidebar, SidebarContent, SidebarContext, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger };
package/dist/slider.js ADDED
@@ -0,0 +1,259 @@
1
+ import { clx, flag, stlx, toNumber } from "./utils.js";
2
+ import { FieldContext } from "./field.js";
3
+ import { callHandler, clamp, controlled, listen, move } from "ajo-cloves";
4
+ import { Fragment, jsx, jsxs } from "ajo/jsx-runtime";
5
+ //#region packages/ajo-ui/src/slider.tsx
6
+ var sliderStep = (value) => value === "any" ? "any" : toNumber(value ?? 1, 1);
7
+ var sliderOrientation = (value) => value === "vertical" ? "vertical" : "horizontal";
8
+ var valueCallback = (value) => typeof value === "function" ? value : void 0;
9
+ var percent = (value, min, max, inverted) => {
10
+ if (max <= min) return 0;
11
+ const raw = clamp((value - min) / (max - min) * 100, 0, 100);
12
+ return inverted ? 100 - raw : raw;
13
+ };
14
+ var precision = (value) => {
15
+ const match = String(value).match(/\.(\d+)/);
16
+ return match ? match[1].length : 0;
17
+ };
18
+ var snap = (value, min, step) => {
19
+ if (step === "any" || step == null) return value;
20
+ const amount = Number(step);
21
+ if (!Number.isFinite(amount) || amount <= 0) return value;
22
+ const places = Math.max(precision(min), precision(amount));
23
+ const next = min + Math.round((value - min) / amount) * amount;
24
+ return Number(next.toFixed(places + 2));
25
+ };
26
+ var valuesFrom = (value, min, max, step, minStepsBetweenThumbs = 0) => {
27
+ const raw = Array.isArray(value) && value.length ? value.map((item) => toNumber(item, min)) : [min];
28
+ const gapStep = step === "any" ? 0 : toNumber(step ?? 1, 1);
29
+ const gap = Math.max(0, minStepsBetweenThumbs) * Math.max(0, gapStep);
30
+ const next = raw.map((item) => snap(clamp(item, min, max), min, step)).sort((a, b) => a - b);
31
+ for (let index = 0; index < next.length; index++) {
32
+ const lower = index > 0 ? next[index - 1] + gap : min;
33
+ next[index] = clamp(next[index], lower, max);
34
+ }
35
+ for (let index = next.length - 1; index >= 0; index--) {
36
+ const upper = index < next.length - 1 ? next[index + 1] - gap : max;
37
+ next[index] = clamp(next[index], min, upper);
38
+ }
39
+ return next;
40
+ };
41
+ var sameValues = (first, second) => first.length === second.length && first.every((value, index) => value === second[index]);
42
+ var replaceValue = (runtime, index, value) => {
43
+ const next = [...runtime.values];
44
+ const gapStep = runtime.step === "any" ? 0 : toNumber(runtime.step ?? 1, 1);
45
+ const gap = Math.max(0, runtime.minStepsBetweenThumbs) * Math.max(0, gapStep);
46
+ next[index] = snap(clamp(value, index > 0 ? next[index - 1] + gap : runtime.min, index < next.length - 1 ? next[index + 1] - gap : runtime.max), runtime.min, runtime.step);
47
+ return next;
48
+ };
49
+ var closestThumb = (runtime, value) => {
50
+ let nearest = 0;
51
+ let distance = Number.POSITIVE_INFINITY;
52
+ for (let index = 0; index < runtime.values.length; index++) {
53
+ const next = Math.abs(runtime.values[index] - value);
54
+ if (next < distance) {
55
+ nearest = index;
56
+ distance = next;
57
+ }
58
+ }
59
+ return nearest;
60
+ };
61
+ var pointerValue = (element, runtime, event) => {
62
+ const rect = element.getBoundingClientRect();
63
+ const distance = runtime.orientation === "vertical" ? rect.bottom - event.clientY : event.clientX - rect.left;
64
+ const size = runtime.orientation === "vertical" ? rect.height : rect.width;
65
+ const raw = size > 0 ? clamp(distance / size, 0, 1) : 0;
66
+ const ratio = runtime.inverted ? 1 - raw : raw;
67
+ return runtime.min + ratio * (runtime.max - runtime.min);
68
+ };
69
+ var rangeStyle = (orientation, start, end) => orientation === "vertical" ? stlx({
70
+ bottom: `${start}%`,
71
+ height: `${Math.max(0, end - start)}%`
72
+ }) : stlx({
73
+ left: `${start}%`,
74
+ width: `${Math.max(0, end - start)}%`
75
+ });
76
+ var thumbStyle = (orientation, position) => orientation === "vertical" ? stlx({
77
+ bottom: `${position}%`,
78
+ left: "50%",
79
+ transform: "translate(-50%, 50%)"
80
+ }) : stlx({
81
+ left: `${position}%`,
82
+ top: "50%",
83
+ transform: "translate(-50%, -50%)"
84
+ });
85
+ var inputId = (id, index, total) => {
86
+ if (id == null) return void 0;
87
+ const value = String(id);
88
+ return total === 1 ? value : `${value}-${index + 1}`;
89
+ };
90
+ var inputLabel = (label, index, total) => {
91
+ if (label == null) return total === 1 ? "Slider" : `Slider thumb ${index + 1}`;
92
+ const value = String(label);
93
+ return total === 1 ? value : `${value} ${index + 1}`;
94
+ };
95
+ var SliderRoot = function* ({ defaultValue, max = 100, min = 0, step = 1, value }) {
96
+ const initialMin = toNumber(min, 0);
97
+ const initialMax = toNumber(max, 100);
98
+ const initialStep = sliderStep(step);
99
+ const initial = valuesFrom(value ?? defaultValue, initialMin, initialMax, initialStep, 0);
100
+ let runtime = {
101
+ disabled: false,
102
+ inputs: [],
103
+ inverted: false,
104
+ max: initialMax,
105
+ min: initialMin,
106
+ minStepsBetweenThumbs: 0,
107
+ orientation: "horizontal",
108
+ step: initialStep,
109
+ values: initial
110
+ };
111
+ const state = controlled(this, {
112
+ fallback: initial,
113
+ onChange: (next, event) => runtime.onValueChange?.(next, event)
114
+ });
115
+ const commit = (event) => runtime.onValueCommit?.(runtime.values, event);
116
+ const update = (index, raw, event) => {
117
+ const next = replaceValue(runtime, index, raw);
118
+ if (sameValues(next, runtime.values)) return;
119
+ runtime.values = next;
120
+ state.set(next, event);
121
+ };
122
+ let dragIndex = 0;
123
+ const drag = move(this, {
124
+ onMove: (_data, event) => update(dragIndex, pointerValue(this, runtime, event), event),
125
+ onEnd: (_data, event) => commit(event)
126
+ });
127
+ listen(this, "pointerdown", (event) => {
128
+ if (runtime.disabled || event.button !== 0) return;
129
+ const raw = pointerValue(this, runtime, event);
130
+ const index = closestThumb(runtime, raw);
131
+ dragIndex = index;
132
+ runtime.inputs[index]?.focus();
133
+ update(index, raw, event);
134
+ event.preventDefault();
135
+ drag.start(event);
136
+ });
137
+ for (const { disabled, inputAttrs, inputClass, inputOnChange, inputOnInput, inverted, max = 100, min = 0, minStepsBetweenThumbs = 0, onValueChange, onValueCommit, orientation = "horizontal", rangeClass, step = 1, thumbClass, trackClass, value, verticalInputClass } of this) {
138
+ const minValue = toNumber(min, 0);
139
+ const maxValue = toNumber(max, 100);
140
+ const stepValue = sliderStep(step);
141
+ const gapValue = toNumber(minStepsBetweenThumbs, 0);
142
+ const orientationValue = sliderOrientation(orientation);
143
+ const raw = state.sync(value != null ? value : void 0);
144
+ const values = valuesFrom(raw, minValue, maxValue, stepValue, gapValue);
145
+ if (!state.controlled && !sameValues(values, raw)) state.init(values);
146
+ runtime = {
147
+ disabled: Boolean(disabled),
148
+ inputs: runtime.inputs,
149
+ inverted: Boolean(inverted),
150
+ max: maxValue,
151
+ min: minValue,
152
+ minStepsBetweenThumbs: gapValue,
153
+ onValueChange: valueCallback(onValueChange),
154
+ onValueCommit: valueCallback(onValueCommit),
155
+ orientation: orientationValue,
156
+ step: stepValue,
157
+ values
158
+ };
159
+ runtime.inputs.length = values.length;
160
+ const positions = values.map((item) => percent(item, minValue, maxValue, runtime.inverted));
161
+ const start = values.length > 1 ? Math.min(...positions) : 0;
162
+ const end = values.length > 1 ? Math.max(...positions) : positions[0] ?? 0;
163
+ const disabledFlag = disabled ? true : void 0;
164
+ yield /* @__PURE__ */ jsxs(Fragment, { children: [
165
+ /* @__PURE__ */ jsx("span", {
166
+ class: trackClass,
167
+ "data-orientation": orientationValue,
168
+ "data-slot": "slider-track",
169
+ children: /* @__PURE__ */ jsx("span", {
170
+ class: rangeClass,
171
+ "data-orientation": orientationValue,
172
+ "data-slot": "slider-range",
173
+ style: rangeStyle(orientationValue, start, end)
174
+ })
175
+ }),
176
+ values.map((item, index) => {
177
+ const handleInput = (event) => {
178
+ callHandler(inputOnInput, event);
179
+ update(index, event.currentTarget.valueAsNumber, event);
180
+ };
181
+ const handleChange = (event) => {
182
+ callHandler(inputOnChange, event);
183
+ commit(event);
184
+ };
185
+ return /* @__PURE__ */ jsx("input", {
186
+ ...inputAttrs,
187
+ "aria-label": inputLabel(inputAttrs["aria-label"], index, values.length),
188
+ "aria-orientation": orientationValue,
189
+ class: clx(inputClass, orientationValue === "vertical" && verticalInputClass),
190
+ "data-orientation": orientationValue,
191
+ "data-slot": "slider-input",
192
+ disabled: disabledFlag,
193
+ id: inputId(inputAttrs.id, index, values.length),
194
+ max: maxValue,
195
+ min: minValue,
196
+ ref: (element) => runtime.inputs[index] = element,
197
+ "set:onchange": handleChange,
198
+ "set:oninput": handleInput,
199
+ "set:value": String(item),
200
+ step: stepValue,
201
+ type: "range",
202
+ value: item
203
+ }, `slider-input-${index}`);
204
+ }),
205
+ positions.map((item, index) => /* @__PURE__ */ jsx("span", {
206
+ "aria-hidden": "true",
207
+ class: thumbClass,
208
+ "data-index": index,
209
+ "data-orientation": orientationValue,
210
+ "data-slot": "slider-thumb",
211
+ style: thumbStyle(orientationValue, item)
212
+ }, `slider-thumb-${index}`))
213
+ ] });
214
+ }
215
+ };
216
+ SliderRoot.is = "span";
217
+ /** Unstyled range slider with native range inputs and pointer behavior. */
218
+ var Slider = ({ class: classes, defaultValue, disabled, inputClass, inverted, max = 100, min = 0, minStepsBetweenThumbs, onValueChange, onValueCommit, orientation = "horizontal", rangeClass, "set:onchange": inputOnChange, "set:oninput": inputOnInput, step = 1, thumbClass, trackClass, type: _type, value, verticalInputClass, ...inputAttrs }) => {
219
+ const disabledFlag = disabled ? true : void 0;
220
+ const preview = valuesFrom(value ?? defaultValue, toNumber(min, 0), toNumber(max, 100), sliderStep(step), toNumber(minStepsBetweenThumbs, 0));
221
+ const field = FieldContext();
222
+ const groupAttrs = field && preview.length > 1 ? field.groupAttrs : void 0;
223
+ const controlAttrs = field && preview.length === 1 ? field.controlAttrs : void 0;
224
+ return /* @__PURE__ */ jsx(SliderRoot, {
225
+ defaultValue,
226
+ disabled: disabledFlag,
227
+ inputAttrs: controlAttrs ? {
228
+ ...controlAttrs,
229
+ ...inputAttrs
230
+ } : inputAttrs,
231
+ inputClass,
232
+ inputOnChange,
233
+ inputOnInput,
234
+ inverted,
235
+ max,
236
+ min,
237
+ minStepsBetweenThumbs,
238
+ onValueChange,
239
+ onValueCommit,
240
+ orientation,
241
+ rangeClass,
242
+ step,
243
+ thumbClass,
244
+ trackClass,
245
+ value,
246
+ verticalInputClass,
247
+ "attr:aria-describedby": groupAttrs?.["aria-describedby"],
248
+ "attr:aria-labelledby": groupAttrs?.["aria-labelledby"],
249
+ "attr:aria-disabled": flag(disabled),
250
+ "attr:class": classes,
251
+ "attr:data-disabled": flag(disabled),
252
+ "attr:data-inverted": flag(inverted),
253
+ "attr:data-orientation": orientation,
254
+ "attr:data-slot": "slider",
255
+ "attr:role": groupAttrs ? "group" : void 0
256
+ });
257
+ };
258
+ //#endregion
259
+ export { Slider };
package/dist/switch.js ADDED
@@ -0,0 +1,53 @@
1
+ import { callHandler, controlled } from "ajo-cloves";
2
+ import { Fragment, jsx, jsxs } from "ajo/jsx-runtime";
3
+ //#region packages/ajo-ui/src/switch.tsx
4
+ var SwitchRoot = function* ({ checked, defaultChecked, setChecked }) {
5
+ let onCheckedChange;
6
+ const state = controlled(this, {
7
+ fallback: Boolean(checked ?? defaultChecked ?? setChecked),
8
+ onChange: (next, event) => onCheckedChange?.(next, event)
9
+ });
10
+ for (const args of this) {
11
+ const initialChecked = args.checked ?? args.defaultChecked;
12
+ onCheckedChange = args.onCheckedChange;
13
+ state.sync(args.checked != null ? Boolean(args.checked) : void 0);
14
+ yield /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
15
+ ...args.inputAttrs,
16
+ checked: initialChecked,
17
+ class: args.inputClass,
18
+ "data-slot": "switch-input",
19
+ role: args.role,
20
+ "set:checked": state.value,
21
+ "set:onchange": (event) => {
22
+ callHandler(args.setOnChange, event);
23
+ state.set(event.currentTarget.checked, event);
24
+ },
25
+ type: "checkbox",
26
+ value: args.value
27
+ }), /* @__PURE__ */ jsx("span", {
28
+ "aria-hidden": "true",
29
+ class: args.thumbClass,
30
+ "data-slot": "switch-thumb"
31
+ })] });
32
+ }
33
+ };
34
+ SwitchRoot.is = "span";
35
+ /** Unstyled native switch control with form behavior. */
36
+ var Switch = ({ checked, class: classes, defaultChecked, inputClass, onCheckedChange, role = "switch", size, thumbClass, "set:checked": setChecked, "set:onchange": setOnChange, type: _type, value = "on", ...attrs }) => /* @__PURE__ */ jsx(SwitchRoot, {
37
+ checked,
38
+ defaultChecked,
39
+ inputAttrs: attrs,
40
+ inputClass,
41
+ onCheckedChange,
42
+ role,
43
+ setChecked,
44
+ setOnChange,
45
+ size,
46
+ thumbClass,
47
+ value,
48
+ "attr:class": classes,
49
+ "attr:data-size": size,
50
+ "attr:data-slot": "switch"
51
+ });
52
+ //#endregion
53
+ export { Switch };