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,446 @@
1
+ import { browser, callHandler, callRef, frame, overflow, resize, scrolling, timer } from "ajo-cloves";
2
+ import { context } from "ajo/context";
3
+ import { Fragment, jsx } from "ajo/jsx-runtime";
4
+ //#region packages/ajo-ui/src/message-scroller.tsx
5
+ /** Read the public controller inherited from the nearest MessageScrollerProvider. */
6
+ var MessageScrollerContext = context(null);
7
+ var MessageScrollerPartsContext = context(null);
8
+ var edge = 2;
9
+ var messageScroller = () => {
10
+ const api = MessageScrollerContext();
11
+ if (!api) throw new Error("MessageScroller controls must be used within a <MessageScrollerProvider />");
12
+ return api;
13
+ };
14
+ var messageScrollerParts = () => {
15
+ const parts = MessageScrollerPartsContext();
16
+ if (!parts) throw new Error("MessageScroller parts must be used within a <MessageScrollerProvider />");
17
+ return parts;
18
+ };
19
+ var getItems = (content) => content ? Array.from(content.querySelectorAll("[data-slot=\"message-scroller-item\"]")) : [];
20
+ var rectTop = (viewport, element) => element.getBoundingClientRect().top - viewport.getBoundingClientRect().top;
21
+ var stamp = (element, name, value) => {
22
+ if (element.getAttribute(name) !== value) element.setAttribute(name, value);
23
+ };
24
+ /** Unstyled provider for imperative scroll behavior and visibility state. */
25
+ var MessageScrollerProvider = function* ({ autoScroll = true, defaultScrollPosition = "end", preserveScrollOnPrepend = true, scrollPreviousItemPeek = 0 }) {
26
+ let root = null;
27
+ let viewport = null;
28
+ let content = null;
29
+ let mutation;
30
+ let initialized = false;
31
+ let didInitialScroll = false;
32
+ let following = defaultScrollPosition !== "start";
33
+ let currentAutoScroll = autoScroll;
34
+ let currentDefaultPosition = defaultScrollPosition;
35
+ let currentPreserve = preserveScrollOnPrepend;
36
+ let currentPeek = scrollPreviousItemPeek;
37
+ let currentSetApi;
38
+ let pending = null;
39
+ let preserveAnchor = null;
40
+ let settles = 0;
41
+ const items = /* @__PURE__ */ new Map();
42
+ const buttons = {
43
+ end: null,
44
+ start: null
45
+ };
46
+ const scrollable = {
47
+ end: false,
48
+ start: false
49
+ };
50
+ const visibility = { visibleMessageIds: [] };
51
+ const autoscroll = timer(this);
52
+ const setAutoscrolling = (active) => {
53
+ if (!viewport || !root) return;
54
+ root.toggleAttribute("data-autoscrolling", active);
55
+ viewport.toggleAttribute("data-autoscrolling", active);
56
+ if (!active) {
57
+ autoscroll.stop();
58
+ return;
59
+ }
60
+ autoscroll.start(1200, () => {
61
+ root?.toggleAttribute("data-autoscrolling", false);
62
+ viewport?.toggleAttribute("data-autoscrolling", false);
63
+ });
64
+ };
65
+ const atStart = () => !viewport || viewport.scrollTop <= edge;
66
+ const atEnd = () => !viewport || viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight <= edge;
67
+ const buttonActive = (direction) => direction === "start" ? scrollable.start : scrollable.end;
68
+ const updateButton = (direction) => {
69
+ const button = buttons[direction];
70
+ if (!button) return;
71
+ const active = buttonActive(direction);
72
+ const tabIndex = active ? 0 : -1;
73
+ stamp(button, "data-active", String(active));
74
+ if (button.tabIndex !== tabIndex) button.tabIndex = tabIndex;
75
+ if (button.hasAttribute("inert") === active) button.toggleAttribute("inert", !active);
76
+ };
77
+ const readEdges = () => {
78
+ const canStart = !atStart();
79
+ return {
80
+ canEnd: !atEnd(),
81
+ canStart
82
+ };
83
+ };
84
+ const commitEdges = ({ canEnd, canStart }) => {
85
+ scrollable.start = canStart;
86
+ scrollable.end = canEnd;
87
+ updateButton("start");
88
+ updateButton("end");
89
+ };
90
+ const updateData = () => commitEdges(readEdges());
91
+ const findItem = (messageId) => {
92
+ const registered = items.get(messageId);
93
+ if (registered) return registered;
94
+ return getItems(content).find((item) => item.dataset.messageId === messageId);
95
+ };
96
+ const readItems = () => {
97
+ if (!viewport || !content) return {
98
+ currentAnchorId: void 0,
99
+ preserveAnchor: null,
100
+ visibleMessageIds: []
101
+ };
102
+ const viewportRect = viewport.getBoundingClientRect();
103
+ const visible = [];
104
+ let anchor;
105
+ let preserved = null;
106
+ for (const item of getItems(content)) {
107
+ const rect = item.getBoundingClientRect();
108
+ const id = item.dataset.messageId;
109
+ const intersects = rect.bottom > viewportRect.top && rect.top < viewportRect.bottom;
110
+ if (intersects && id) visible.push(id);
111
+ if (currentPreserve && !preserveAnchor && !preserved && intersects && id) preserved = {
112
+ id,
113
+ top: rect.top - viewportRect.top
114
+ };
115
+ if (id && item.dataset.scrollAnchor === "true" && rect.top <= viewportRect.top + viewport.clientHeight * .65) anchor = id;
116
+ }
117
+ return {
118
+ currentAnchorId: anchor,
119
+ preserveAnchor: preserved,
120
+ visibleMessageIds: visible
121
+ };
122
+ };
123
+ const commitItems = (reading) => {
124
+ visibility.currentAnchorId = reading.currentAnchorId;
125
+ visibility.visibleMessageIds = reading.visibleMessageIds;
126
+ if (!currentPreserve) preserveAnchor = null;
127
+ else preserveAnchor ??= reading.preserveAnchor;
128
+ };
129
+ const sync = () => {
130
+ const edges = readEdges();
131
+ commitItems(readItems());
132
+ commitEdges(edges);
133
+ };
134
+ const schedule = frame(sync);
135
+ const handleViewportScroll = (element) => {
136
+ if (!element.hasAttribute("data-autoscrolling")) following = atEnd();
137
+ preserveAnchor = null;
138
+ sync();
139
+ };
140
+ const handleViewportEnd = (element) => {
141
+ if (following && !atEnd() && settles < 10) {
142
+ settles += 1;
143
+ setAutoscrolling(true);
144
+ element.scrollTo({
145
+ behavior: "auto",
146
+ top: element.scrollHeight
147
+ });
148
+ } else setAutoscrolling(false);
149
+ schedule();
150
+ };
151
+ const viewportScroll = scrolling(this, {
152
+ target: () => viewport,
153
+ onScroll: handleViewportScroll,
154
+ onEnd: handleViewportEnd
155
+ });
156
+ const viewportSize = resize(this, {
157
+ target: () => viewport,
158
+ onResize: sync
159
+ });
160
+ const edges = overflow(this, { target: () => viewport });
161
+ const scrollTopFor = (element, options = {}) => {
162
+ if (!viewport) return 0;
163
+ const peek = options.peek ?? currentPeek;
164
+ const top = rectTop(viewport, element) + viewport.scrollTop;
165
+ if (options.position === "center") return top - (viewport.clientHeight - element.offsetHeight) / 2;
166
+ if (options.position === "end") return top - viewport.clientHeight + element.offsetHeight + peek;
167
+ return top - peek;
168
+ };
169
+ const scrollToTop = (top, behavior = "smooth") => {
170
+ if (!viewport) return false;
171
+ preserveAnchor = null;
172
+ settles = 0;
173
+ setAutoscrolling(true);
174
+ viewport.scrollTo({
175
+ behavior,
176
+ top: Math.max(0, top)
177
+ });
178
+ schedule();
179
+ return true;
180
+ };
181
+ const scrollToStart = (options = {}) => {
182
+ following = false;
183
+ return scrollToTop(0, options.behavior);
184
+ };
185
+ const scrollToEnd = (options = {}) => {
186
+ if (!viewport) return false;
187
+ following = true;
188
+ return scrollToTop(viewport.scrollHeight, options.behavior);
189
+ };
190
+ const scrollToMessage = (messageId, options = {}) => {
191
+ const item = findItem(messageId);
192
+ if (!item) {
193
+ if (!initialized) {
194
+ pending = {
195
+ id: messageId,
196
+ options
197
+ };
198
+ return true;
199
+ }
200
+ return false;
201
+ }
202
+ following = false;
203
+ return scrollToTop(scrollTopFor(item, options), options.behavior);
204
+ };
205
+ const flushPending = () => {
206
+ if (!pending) return;
207
+ const target = pending;
208
+ pending = null;
209
+ if (!scrollToMessage(target.id, {
210
+ behavior: "auto",
211
+ ...target.options
212
+ })) pending = target;
213
+ };
214
+ const restoreAnchor = () => {
215
+ if (!viewport || !preserveAnchor || following || !currentPreserve) return;
216
+ const item = findItem(preserveAnchor.id);
217
+ if (!item) {
218
+ preserveAnchor = null;
219
+ return;
220
+ }
221
+ const nextTop = rectTop(viewport, item);
222
+ viewport.scrollTop += nextTop - preserveAnchor.top;
223
+ };
224
+ const handleContentChange = () => {
225
+ queueMicrotask(() => {
226
+ restoreAnchor();
227
+ if (currentAutoScroll && following) scrollToEnd({ behavior: "auto" });
228
+ flushPending();
229
+ edges.sync();
230
+ schedule();
231
+ });
232
+ };
233
+ const contentSize = resize(this, {
234
+ target: () => content,
235
+ onResize: handleContentChange
236
+ });
237
+ const applyInitialScroll = () => {
238
+ if (didInitialScroll || !viewport) return;
239
+ didInitialScroll = true;
240
+ initialized = true;
241
+ if (pending) flushPending();
242
+ else if (currentDefaultPosition === "start") scrollToStart({ behavior: "auto" });
243
+ else if (currentDefaultPosition === "last-anchor") {
244
+ const anchor = getItems(content).filter((item) => item.dataset.scrollAnchor === "true" && item.dataset.messageId).at(-1);
245
+ if (anchor?.dataset.messageId) scrollToMessage(anchor.dataset.messageId, {
246
+ behavior: "auto",
247
+ peek: currentPeek
248
+ });
249
+ else scrollToEnd({ behavior: "auto" });
250
+ } else scrollToEnd({ behavior: "auto" });
251
+ sync();
252
+ };
253
+ const scheduleInitial = frame(applyInitialScroll);
254
+ const scheduleInitialScroll = () => {
255
+ if (!browser() || didInitialScroll) return;
256
+ scheduleInitial();
257
+ };
258
+ const setRoot = (element) => {
259
+ root = element;
260
+ updateData();
261
+ };
262
+ const setViewport = (element) => {
263
+ if (viewport === element) return;
264
+ viewport = element;
265
+ viewportScroll.sync();
266
+ viewportSize.sync();
267
+ edges.sync();
268
+ if (!element) {
269
+ updateData();
270
+ return;
271
+ }
272
+ updateData();
273
+ scheduleInitialScroll();
274
+ };
275
+ const setContent = (element) => {
276
+ if (content === element) return;
277
+ mutation?.disconnect();
278
+ content = element;
279
+ contentSize.sync();
280
+ edges.sync();
281
+ if (!element) {
282
+ updateData();
283
+ return;
284
+ }
285
+ if (typeof MutationObserver !== "undefined") {
286
+ mutation = new MutationObserver(handleContentChange);
287
+ mutation.observe(element, {
288
+ childList: true,
289
+ subtree: true
290
+ });
291
+ }
292
+ updateData();
293
+ scheduleInitialScroll();
294
+ };
295
+ const setItem = (messageId, element) => {
296
+ if (element) {
297
+ for (const [registeredId, registered] of items) if (registered === element && registeredId !== messageId) items.delete(registeredId);
298
+ if (messageId) {
299
+ items.set(messageId, element);
300
+ flushPending();
301
+ }
302
+ } else if (messageId) items.delete(messageId);
303
+ schedule();
304
+ };
305
+ const setButton = (direction, element) => {
306
+ if (element) {
307
+ for (const registeredDirection of ["start", "end"]) if (registeredDirection !== direction && buttons[registeredDirection] === element) buttons[registeredDirection] = null;
308
+ }
309
+ buttons[direction] = element;
310
+ updateButton("start");
311
+ updateButton("end");
312
+ };
313
+ const publicApi = {
314
+ scrollToEnd,
315
+ scrollToMessage,
316
+ scrollToStart,
317
+ scrollable,
318
+ visibility
319
+ };
320
+ const parts = {
321
+ setButton,
322
+ setContent,
323
+ setItem,
324
+ setRoot,
325
+ setViewport
326
+ };
327
+ this.signal.addEventListener("abort", () => {
328
+ schedule.cancel();
329
+ scheduleInitial.cancel();
330
+ mutation?.disconnect();
331
+ });
332
+ for (const { autoScroll = true, children, defaultScrollPosition = "end", preserveScrollOnPrepend = true, scrollPreviousItemPeek = 0, setApi } of this) {
333
+ const wasPreserving = currentPreserve;
334
+ currentAutoScroll = autoScroll;
335
+ currentDefaultPosition = defaultScrollPosition;
336
+ currentPreserve = preserveScrollOnPrepend;
337
+ if (!currentPreserve) preserveAnchor = null;
338
+ else if (!wasPreserving && !following) preserveAnchor = readItems().preserveAnchor;
339
+ currentPeek = scrollPreviousItemPeek;
340
+ if (setApi && setApi !== currentSetApi) {
341
+ currentSetApi = setApi;
342
+ setApi(publicApi);
343
+ }
344
+ viewportScroll.sync();
345
+ viewportSize.sync();
346
+ edges.sync();
347
+ contentSize.sync();
348
+ MessageScrollerContext(publicApi);
349
+ MessageScrollerPartsContext(parts);
350
+ queueMicrotask(scheduleInitialScroll);
351
+ yield /* @__PURE__ */ jsx(Fragment, { children });
352
+ }
353
+ };
354
+ MessageScrollerProvider.attrs = { "data-slot": "message-scroller-provider" };
355
+ /** Unstyled root container for a scroll-managed message transcript. */
356
+ var MessageScroller = ({ children, ref, ...attrs }) => {
357
+ const parts = messageScrollerParts();
358
+ return /* @__PURE__ */ jsx("div", {
359
+ ...attrs,
360
+ "data-slot": "message-scroller",
361
+ ref: (element) => {
362
+ parts.setRoot(element);
363
+ callRef(ref, element);
364
+ },
365
+ children
366
+ });
367
+ };
368
+ /** Unstyled keyboard-focusable transcript viewport. */
369
+ var MessageScrollerViewport = ({ children, role = "region", tabIndex = 0, "aria-label": label = "Messages", ref, ...attrs }) => {
370
+ const parts = messageScrollerParts();
371
+ return /* @__PURE__ */ jsx("div", {
372
+ ...attrs,
373
+ "aria-label": label,
374
+ "data-slot": "message-scroller-viewport",
375
+ ref: (element) => {
376
+ parts.setViewport(element);
377
+ callRef(ref, element);
378
+ },
379
+ role,
380
+ tabIndex,
381
+ children
382
+ });
383
+ };
384
+ /** Unstyled live-region content list for rendered messages. */
385
+ var MessageScrollerContent = ({ children, role = "log", "aria-live": live = "polite", "aria-relevant": relevant = "additions", ref, ...attrs }) => {
386
+ const parts = messageScrollerParts();
387
+ return /* @__PURE__ */ jsx("div", {
388
+ ...attrs,
389
+ "aria-live": live,
390
+ "aria-relevant": relevant,
391
+ "data-slot": "message-scroller-content",
392
+ ref: (element) => {
393
+ parts.setContent(element);
394
+ callRef(ref, element);
395
+ },
396
+ role,
397
+ children
398
+ });
399
+ };
400
+ /** Unstyled scroll-trackable transcript item. */
401
+ var MessageScrollerItem = ({ children, messageId, ref, scrollAnchor = false, ...attrs }) => {
402
+ const parts = messageScrollerParts();
403
+ return /* @__PURE__ */ jsx("div", {
404
+ ...attrs,
405
+ "data-message-id": messageId,
406
+ "data-scroll-anchor": String(scrollAnchor),
407
+ "data-slot": "message-scroller-item",
408
+ ref: (element) => {
409
+ parts.setItem(messageId, element);
410
+ callRef(ref, element);
411
+ },
412
+ children
413
+ });
414
+ };
415
+ /** Unstyled floating button that jumps to the start or end of the transcript. */
416
+ var MessageScrollerButton = ({ "aria-label": label, children, direction = "end", disabled, ref, type = "button", "set:onclick": onClick, ...attrs }) => {
417
+ const scroller = messageScroller();
418
+ const parts = messageScrollerParts();
419
+ const buttonActive = (next) => next === "start" ? scroller.scrollable.start : scroller.scrollable.end;
420
+ const active = buttonActive(direction);
421
+ const title = label ?? (direction === "end" ? "Scroll to end" : "Scroll to start");
422
+ const click = (event) => {
423
+ callHandler(onClick, event);
424
+ if (event.defaultPrevented || disabled || !buttonActive(direction)) return;
425
+ if (direction === "start") scroller.scrollToStart();
426
+ else scroller.scrollToEnd();
427
+ };
428
+ return /* @__PURE__ */ jsx("button", {
429
+ ...attrs,
430
+ "aria-label": title,
431
+ "data-active": active ? "true" : "false",
432
+ "data-direction": direction,
433
+ "data-slot": "message-scroller-button",
434
+ disabled,
435
+ ref: (element) => {
436
+ parts.setButton(direction, element);
437
+ callRef(ref, element);
438
+ },
439
+ "set:onclick": click,
440
+ tabIndex: active ? attrs.tabIndex : -1,
441
+ type,
442
+ children
443
+ });
444
+ };
445
+ //#endregion
446
+ export { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerContext, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport };