@ansh-ui/vue 0.2.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-2VSROZ2N.js +1281 -0
  3. package/dist/chunk-2VSROZ2N.js.map +1 -0
  4. package/dist/chunk-5BQEZYG6.cjs +1294 -0
  5. package/dist/chunk-5BQEZYG6.cjs.map +1 -0
  6. package/dist/chunk-6732H4BQ.cjs +28 -0
  7. package/dist/chunk-6732H4BQ.cjs.map +1 -0
  8. package/dist/chunk-6ABPGOJP.js +722 -0
  9. package/dist/chunk-6ABPGOJP.js.map +1 -0
  10. package/dist/chunk-6XJOB6LE.js +62 -0
  11. package/dist/chunk-6XJOB6LE.js.map +1 -0
  12. package/dist/chunk-774WBLDR.cjs +24 -0
  13. package/dist/chunk-774WBLDR.cjs.map +1 -0
  14. package/dist/chunk-B6OXKW5Q.js +567 -0
  15. package/dist/chunk-B6OXKW5Q.js.map +1 -0
  16. package/dist/chunk-DMPNT3MY.cjs +64 -0
  17. package/dist/chunk-DMPNT3MY.cjs.map +1 -0
  18. package/dist/chunk-EISHOEB6.cjs +158 -0
  19. package/dist/chunk-EISHOEB6.cjs.map +1 -0
  20. package/dist/chunk-EVO23GUS.js +223 -0
  21. package/dist/chunk-EVO23GUS.js.map +1 -0
  22. package/dist/chunk-FVKLCGA4.js +1035 -0
  23. package/dist/chunk-FVKLCGA4.js.map +1 -0
  24. package/dist/chunk-IOXGBPOV.cjs +3681 -0
  25. package/dist/chunk-IOXGBPOV.cjs.map +1 -0
  26. package/dist/chunk-J2LA4GRP.cjs +1051 -0
  27. package/dist/chunk-J2LA4GRP.cjs.map +1 -0
  28. package/dist/chunk-JQ4YLXXM.cjs +580 -0
  29. package/dist/chunk-JQ4YLXXM.cjs.map +1 -0
  30. package/dist/chunk-KCA7RNJ4.cjs +781 -0
  31. package/dist/chunk-KCA7RNJ4.cjs.map +1 -0
  32. package/dist/chunk-NJDDNO4C.js +23 -0
  33. package/dist/chunk-NJDDNO4C.js.map +1 -0
  34. package/dist/chunk-OAQ7MDYX.js +22 -0
  35. package/dist/chunk-OAQ7MDYX.js.map +1 -0
  36. package/dist/chunk-PP6OYURM.cjs +238 -0
  37. package/dist/chunk-PP6OYURM.cjs.map +1 -0
  38. package/dist/chunk-SHX2JGID.cjs +269 -0
  39. package/dist/chunk-SHX2JGID.cjs.map +1 -0
  40. package/dist/chunk-SOYRICHU.js +3645 -0
  41. package/dist/chunk-SOYRICHU.js.map +1 -0
  42. package/dist/chunk-T425FIC7.js +773 -0
  43. package/dist/chunk-T425FIC7.js.map +1 -0
  44. package/dist/chunk-TMG3M6TK.js +152 -0
  45. package/dist/chunk-TMG3M6TK.js.map +1 -0
  46. package/dist/chunk-XLLSS464.cjs +286 -0
  47. package/dist/chunk-XLLSS464.cjs.map +1 -0
  48. package/dist/chunk-XZ5ZFLZM.js +280 -0
  49. package/dist/chunk-XZ5ZFLZM.js.map +1 -0
  50. package/dist/chunk-Z7XOFKM4.cjs +733 -0
  51. package/dist/chunk-Z7XOFKM4.cjs.map +1 -0
  52. package/dist/chunk-ZGMXOMCB.js +266 -0
  53. package/dist/chunk-ZGMXOMCB.js.map +1 -0
  54. package/dist/data.cjs +70 -0
  55. package/dist/data.cjs.map +1 -0
  56. package/dist/data.d.cts +963 -0
  57. package/dist/data.d.ts +963 -0
  58. package/dist/data.js +5 -0
  59. package/dist/data.js.map +1 -0
  60. package/dist/form.cjs +151 -0
  61. package/dist/form.cjs.map +1 -0
  62. package/dist/form.d.cts +2639 -0
  63. package/dist/form.d.ts +2639 -0
  64. package/dist/form.js +6 -0
  65. package/dist/form.js.map +1 -0
  66. package/dist/index.cjs +1158 -0
  67. package/dist/index.cjs.map +1 -0
  68. package/dist/index.d.cts +439 -0
  69. package/dist/index.d.ts +439 -0
  70. package/dist/index.js +547 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/layout.cjs +67 -0
  73. package/dist/layout.cjs.map +1 -0
  74. package/dist/layout.d.cts +729 -0
  75. package/dist/layout.d.ts +729 -0
  76. package/dist/layout.js +6 -0
  77. package/dist/layout.js.map +1 -0
  78. package/dist/motion.cjs +50 -0
  79. package/dist/motion.cjs.map +1 -0
  80. package/dist/motion.d.cts +681 -0
  81. package/dist/motion.d.ts +681 -0
  82. package/dist/motion.js +5 -0
  83. package/dist/motion.js.map +1 -0
  84. package/dist/navigation.cjs +48 -0
  85. package/dist/navigation.cjs.map +1 -0
  86. package/dist/navigation.d.cts +515 -0
  87. package/dist/navigation.d.ts +515 -0
  88. package/dist/navigation.js +7 -0
  89. package/dist/navigation.js.map +1 -0
  90. package/dist/overlay.cjs +61 -0
  91. package/dist/overlay.cjs.map +1 -0
  92. package/dist/overlay.d.cts +772 -0
  93. package/dist/overlay.d.ts +772 -0
  94. package/dist/overlay.js +8 -0
  95. package/dist/overlay.js.map +1 -0
  96. package/dist/position-Cn7qP1pn.d.cts +50 -0
  97. package/dist/position-Cn7qP1pn.d.ts +50 -0
  98. package/dist/primitives.cjs +129 -0
  99. package/dist/primitives.cjs.map +1 -0
  100. package/dist/primitives.d.cts +300 -0
  101. package/dist/primitives.d.ts +300 -0
  102. package/dist/primitives.js +8 -0
  103. package/dist/primitives.js.map +1 -0
  104. package/dist/sidebar-CQqEmpid.d.cts +124 -0
  105. package/dist/sidebar-CTZ-Ckko.d.ts +124 -0
  106. package/dist/styles.css +5593 -0
  107. package/dist/utils-IKPG3zcC.d.cts +46 -0
  108. package/dist/utils-IKPG3zcC.d.ts +46 -0
  109. package/package.json +91 -0
@@ -0,0 +1,1035 @@
1
+ import { useControllableState } from './chunk-OAQ7MDYX.js';
2
+ import { cx, resolveSlotContent } from './chunk-NJDDNO4C.js';
3
+ import { defineComponent, computed, h, ref, onBeforeUnmount } from 'vue';
4
+ import { toHex, parseColor, pickReadable } from '@ansh-ui/core';
5
+
6
+ var DATA_TABLE_PROP_KEYS = [
7
+ "data",
8
+ "columns",
9
+ "getRowId",
10
+ "caption",
11
+ "selectable",
12
+ "selected",
13
+ "sort",
14
+ "defaultSort",
15
+ "pageSize",
16
+ "page",
17
+ "loading",
18
+ "emptyState",
19
+ "onRowClick",
20
+ "stickyHeader",
21
+ "density",
22
+ "striped",
23
+ "rowClassName"
24
+ ];
25
+ function compare(a, b) {
26
+ if (a == null && b == null) return 0;
27
+ if (a == null) return 1;
28
+ if (b == null) return -1;
29
+ if (typeof a === "number" && typeof b === "number") return a - b;
30
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
31
+ return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
32
+ }
33
+ var DataTable = defineComponent(
34
+ (props, { emit, attrs }) => {
35
+ const [sortState, setSortState] = useControllableState(
36
+ () => props.sort,
37
+ props.defaultSort ?? null,
38
+ (next) => emit("update:sort", next)
39
+ );
40
+ const [selection, setSelection] = useControllableState(
41
+ () => props.selected,
42
+ [],
43
+ (next) => emit("update:selected", next)
44
+ );
45
+ const [currentPage, setCurrentPage] = useControllableState(
46
+ () => props.page,
47
+ 0,
48
+ (next) => emit("update:page", next)
49
+ );
50
+ const sorted = computed(() => {
51
+ const state = sortState.value;
52
+ if (!state) return props.data;
53
+ const column = props.columns.find((c) => c.id === state.columnId);
54
+ const sortValue = column?.sortValue;
55
+ if (!sortValue) return props.data;
56
+ const rows = [...props.data];
57
+ rows.sort((a, b) => {
58
+ const result = compare(sortValue(a), sortValue(b));
59
+ return state.direction === "asc" ? result : -result;
60
+ });
61
+ return rows;
62
+ });
63
+ const pageCount = computed(
64
+ () => props.pageSize ? Math.max(1, Math.ceil(sorted.value.length / props.pageSize)) : 1
65
+ );
66
+ const safePage = computed(() => Math.min(currentPage.value, pageCount.value - 1));
67
+ const visible = computed(() => {
68
+ const pageSize = props.pageSize;
69
+ if (!pageSize) return sorted.value;
70
+ const start = safePage.value * pageSize;
71
+ return sorted.value.slice(start, start + pageSize);
72
+ });
73
+ const visibleIds = computed(() => visible.value.map(props.getRowId));
74
+ const selectedSet = computed(() => new Set(selection.value));
75
+ const allVisibleSelected = computed(
76
+ () => visibleIds.value.length > 0 && visibleIds.value.every((id) => selectedSet.value.has(id))
77
+ );
78
+ const someVisibleSelected = computed(
79
+ () => visibleIds.value.some((id) => selectedSet.value.has(id))
80
+ );
81
+ const toggleAll = () => {
82
+ if (allVisibleSelected.value) {
83
+ setSelection(selection.value.filter((id) => !visibleIds.value.includes(id)));
84
+ } else {
85
+ setSelection([.../* @__PURE__ */ new Set([...selection.value, ...visibleIds.value])]);
86
+ }
87
+ };
88
+ const toggleRow = (rowId) => {
89
+ setSelection(
90
+ selectedSet.value.has(rowId) ? selection.value.filter((v) => v !== rowId) : [...selection.value, rowId]
91
+ );
92
+ };
93
+ const onSort = (column) => {
94
+ if (!column.sortable) return;
95
+ const state = sortState.value;
96
+ if (state?.columnId !== column.id) {
97
+ setSortState({ columnId: column.id, direction: "asc" });
98
+ } else if (state.direction === "asc") {
99
+ setSortState({ columnId: column.id, direction: "desc" });
100
+ } else {
101
+ setSortState(null);
102
+ }
103
+ };
104
+ return () => {
105
+ const columns = props.columns;
106
+ const selectable = props.selectable ?? false;
107
+ const density = props.density ?? "comfortable";
108
+ const striped = props.striped ?? false;
109
+ const columnCount = columns.length + (selectable ? 1 : 0);
110
+ const onRowClick = props.onRowClick;
111
+ const renderBody = () => {
112
+ if (props.loading) {
113
+ return Array.from(
114
+ { length: props.pageSize ?? 5 },
115
+ (_, rowIndex) => h("tr", { key: `skeleton-${rowIndex}`, class: "ansh-table__row" }, [
116
+ ...Array.from(
117
+ { length: columnCount },
118
+ (_2, cellIndex) => h("td", { key: cellIndex, class: "ansh-table__cell" }, [
119
+ h("span", {
120
+ class: "ansh-skeleton",
121
+ style: { width: `${50 + cellIndex * 37 % 40}%`, height: "1em" }
122
+ })
123
+ ])
124
+ )
125
+ ])
126
+ );
127
+ }
128
+ if (visible.value.length === 0) {
129
+ return [
130
+ h("tr", {}, [
131
+ h("td", { colspan: columnCount, class: "ansh-table__empty" }, [
132
+ props.emptyState ? resolveSlotContent(props.emptyState) : "No results"
133
+ ])
134
+ ])
135
+ ];
136
+ }
137
+ return visible.value.map((row, index) => {
138
+ const rowId = props.getRowId(row);
139
+ const isSelected = selectedSet.value.has(rowId);
140
+ return h(
141
+ "tr",
142
+ {
143
+ key: rowId,
144
+ class: cx(
145
+ "ansh-table__row",
146
+ isSelected && "ansh-table__row--selected",
147
+ Boolean(onRowClick) && "ansh-table__row--clickable",
148
+ props.rowClassName?.(row)
149
+ ),
150
+ "aria-selected": selectable ? isSelected : void 0,
151
+ // A click that started on a control inside the row belongs to that control —
152
+ // opening a detail view because someone ticked a checkbox is the classic
153
+ // clickable-row bug.
154
+ onClick: onRowClick ? (event) => {
155
+ const target = event.target;
156
+ if (target.closest("button, a, input, select, textarea")) return;
157
+ onRowClick(row);
158
+ } : void 0
159
+ },
160
+ [
161
+ selectable ? h("td", { class: "ansh-table__cell ansh-table__cell--check" }, [
162
+ h("input", {
163
+ type: "checkbox",
164
+ class: "ansh-table__checkbox",
165
+ checked: isSelected,
166
+ "aria-label": `Select row ${index + 1}`,
167
+ onChange: () => toggleRow(rowId)
168
+ })
169
+ ]) : null,
170
+ ...columns.map(
171
+ (column) => h(
172
+ "td",
173
+ {
174
+ key: column.id,
175
+ class: cx(
176
+ "ansh-table__cell",
177
+ column.align && `ansh-table__cell--${column.align}`,
178
+ column.hideBelow && `ansh-hide-below-${column.hideBelow}`
179
+ )
180
+ },
181
+ [column.cell(row, index)]
182
+ )
183
+ )
184
+ ]
185
+ );
186
+ });
187
+ };
188
+ return h(
189
+ "div",
190
+ {
191
+ class: cx(
192
+ "ansh-table-wrap",
193
+ props.stickyHeader && "ansh-table-wrap--sticky",
194
+ attrs.class
195
+ )
196
+ },
197
+ [
198
+ h(
199
+ "table",
200
+ { class: cx("ansh-table", `ansh-table--${density}`, striped && "ansh-table--striped") },
201
+ [
202
+ h("caption", { class: "ansh-visually-hidden" }, [
203
+ props.caption,
204
+ sortState.value ? `, sorted by ${columns.find((c) => c.id === sortState.value.columnId)?.id} ${sortState.value.direction === "asc" ? "ascending" : "descending"}` : ""
205
+ ]),
206
+ h("thead", { class: "ansh-table__head" }, [
207
+ h("tr", {}, [
208
+ selectable ? h("th", { scope: "col", class: "ansh-table__cell ansh-table__cell--check" }, [
209
+ h("input", {
210
+ type: "checkbox",
211
+ class: "ansh-table__checkbox",
212
+ checked: allVisibleSelected.value,
213
+ // Mixed state when only some rows are ticked — announced as "partially
214
+ // checked" rather than lying about the state. A fresh function
215
+ // identity every render is what makes Vue re-invoke this ref callback
216
+ // (mirroring the React version's inline ref) so the flag stays current.
217
+ ref: (el) => {
218
+ if (el) {
219
+ el.indeterminate = !allVisibleSelected.value && someVisibleSelected.value;
220
+ }
221
+ },
222
+ "aria-label": allVisibleSelected.value ? "Deselect all rows" : "Select all rows",
223
+ onChange: toggleAll
224
+ })
225
+ ]) : null,
226
+ ...columns.map((column) => {
227
+ const isSorted = sortState.value?.columnId === column.id;
228
+ return h(
229
+ "th",
230
+ {
231
+ key: column.id,
232
+ scope: "col",
233
+ class: cx(
234
+ "ansh-table__cell",
235
+ "ansh-table__cell--head",
236
+ column.align && `ansh-table__cell--${column.align}`,
237
+ column.hideBelow && `ansh-hide-below-${column.hideBelow}`
238
+ ),
239
+ style: column.width ? { width: column.width } : void 0,
240
+ // `aria-sort` on the header is what makes a screen reader announce the
241
+ // sort state; the arrow glyph alone conveys nothing.
242
+ "aria-sort": isSorted ? sortState.value.direction === "asc" ? "ascending" : "descending" : void 0
243
+ },
244
+ [
245
+ column.sortable ? h(
246
+ "button",
247
+ {
248
+ type: "button",
249
+ class: "ansh-table__sort ansh-focusable",
250
+ onClick: () => onSort(column),
251
+ "aria-label": column.ariaLabel ? `Sort by ${column.ariaLabel}` : typeof column.header === "string" ? `Sort by ${column.header}` : "Sort column"
252
+ },
253
+ [
254
+ h("span", {}, [resolveSlotContent(column.header)]),
255
+ h(
256
+ "span",
257
+ {
258
+ class: cx(
259
+ "ansh-table__arrow",
260
+ isSorted && "ansh-table__arrow--on"
261
+ ),
262
+ "aria-hidden": "true",
263
+ "data-direction": isSorted ? sortState.value.direction : "none"
264
+ },
265
+ [
266
+ h("svg", { viewBox: "0 0 10 12", width: "10", height: "12" }, [
267
+ h("path", {
268
+ d: "M5 1v10M2 4l3-3 3 3M2 8l3 3 3-3",
269
+ stroke: "currentColor",
270
+ "stroke-width": "1.4",
271
+ fill: "none",
272
+ "stroke-linecap": "round",
273
+ "stroke-linejoin": "round"
274
+ })
275
+ ])
276
+ ]
277
+ )
278
+ ]
279
+ ) : resolveSlotContent(column.header)
280
+ ]
281
+ );
282
+ })
283
+ ])
284
+ ]),
285
+ h("tbody", {}, renderBody())
286
+ ]
287
+ ),
288
+ // Selection count and page changes announced without stealing focus.
289
+ h(
290
+ "span",
291
+ { class: "ansh-visually-hidden", role: "status", "aria-live": "polite" },
292
+ selectable && selection.value.length > 0 ? `${selection.value.length} selected` : ""
293
+ ),
294
+ props.pageSize && pageCount.value > 1 ? h(Pagination, {
295
+ page: safePage.value,
296
+ pageCount: pageCount.value,
297
+ total: sorted.value.length,
298
+ pageSize: props.pageSize,
299
+ onPageChange: setCurrentPage
300
+ }) : null
301
+ ]
302
+ );
303
+ };
304
+ },
305
+ {
306
+ name: "AnshDataTable",
307
+ inheritAttrs: false,
308
+ props: DATA_TABLE_PROP_KEYS,
309
+ emits: ["update:sort", "update:selected", "update:page"]
310
+ }
311
+ );
312
+ var Pagination = defineComponent({
313
+ name: "AnshPagination",
314
+ inheritAttrs: false,
315
+ props: {
316
+ page: { type: Number, required: true },
317
+ pageCount: { type: Number, required: true },
318
+ onPageChange: { type: Function, required: true },
319
+ total: { type: Number, default: void 0 },
320
+ pageSize: { type: Number, default: void 0 },
321
+ siblings: { type: Number, default: 1 }
322
+ },
323
+ setup(props, { attrs }) {
324
+ return () => {
325
+ const pages = [];
326
+ const first = 0;
327
+ const last = props.pageCount - 1;
328
+ for (let index = first; index <= last; index++) {
329
+ const nearCurrent = Math.abs(index - props.page) <= props.siblings;
330
+ const isEdge = index === first || index === last;
331
+ if (nearCurrent || isEdge) {
332
+ pages.push(index);
333
+ } else if (pages[pages.length - 1] !== "gap") {
334
+ pages.push("gap");
335
+ }
336
+ }
337
+ const from = props.pageSize ? props.page * props.pageSize + 1 : null;
338
+ const to = props.pageSize && props.total ? Math.min((props.page + 1) * props.pageSize, props.total) : null;
339
+ return h(
340
+ "nav",
341
+ { class: cx("ansh-pagination", attrs.class), "aria-label": "Pagination" },
342
+ [
343
+ from !== null && to !== null ? h("p", { class: "ansh-pagination__summary" }, `${from}\u2013${to} of ${props.total}`) : null,
344
+ h("ol", { class: "ansh-pagination__list" }, [
345
+ h("li", {}, [
346
+ h(
347
+ "button",
348
+ {
349
+ type: "button",
350
+ class: "ansh-pagination__button ansh-focusable",
351
+ disabled: props.page === 0,
352
+ "aria-label": "Previous page",
353
+ onClick: () => props.onPageChange(props.page - 1)
354
+ },
355
+ [
356
+ h(
357
+ "svg",
358
+ { viewBox: "0 0 12 12", width: "12", height: "12", "aria-hidden": "true" },
359
+ [
360
+ h("path", {
361
+ d: "M7.5 2.5L4 6l3.5 3.5",
362
+ stroke: "currentColor",
363
+ "stroke-width": "1.5",
364
+ fill: "none",
365
+ "stroke-linecap": "round",
366
+ "stroke-linejoin": "round"
367
+ })
368
+ ]
369
+ )
370
+ ]
371
+ )
372
+ ]),
373
+ ...pages.map(
374
+ (entry, index) => entry === "gap" ? h(
375
+ "li",
376
+ { key: `gap-${index}`, "aria-hidden": "true", class: "ansh-pagination__gap" },
377
+ "\u2026"
378
+ ) : h("li", { key: entry }, [
379
+ h(
380
+ "button",
381
+ {
382
+ type: "button",
383
+ class: cx(
384
+ "ansh-pagination__button",
385
+ entry === props.page && "ansh-pagination__button--current",
386
+ "ansh-focusable"
387
+ ),
388
+ "aria-current": entry === props.page ? "page" : void 0,
389
+ "aria-label": `Page ${entry + 1}`,
390
+ onClick: () => props.onPageChange(entry)
391
+ },
392
+ String(entry + 1)
393
+ )
394
+ ])
395
+ ),
396
+ h("li", {}, [
397
+ h(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ class: "ansh-pagination__button ansh-focusable",
402
+ disabled: props.page >= props.pageCount - 1,
403
+ "aria-label": "Next page",
404
+ onClick: () => props.onPageChange(props.page + 1)
405
+ },
406
+ [
407
+ h(
408
+ "svg",
409
+ { viewBox: "0 0 12 12", width: "12", height: "12", "aria-hidden": "true" },
410
+ [
411
+ h("path", {
412
+ d: "M4.5 2.5L8 6l-3.5 3.5",
413
+ stroke: "currentColor",
414
+ "stroke-width": "1.5",
415
+ fill: "none",
416
+ "stroke-linecap": "round",
417
+ "stroke-linejoin": "round"
418
+ })
419
+ ]
420
+ )
421
+ ]
422
+ )
423
+ ])
424
+ ])
425
+ ]
426
+ );
427
+ };
428
+ }
429
+ });
430
+ var Table = defineComponent({
431
+ name: "AnshTable",
432
+ inheritAttrs: false,
433
+ props: {
434
+ caption: { type: String, default: void 0 },
435
+ density: { type: String, default: "comfortable" },
436
+ striped: { type: Boolean, default: false }
437
+ },
438
+ setup(props, { slots, attrs }) {
439
+ return () => h("div", { class: cx("ansh-table-wrap", attrs.class) }, [
440
+ h(
441
+ "table",
442
+ {
443
+ class: cx(
444
+ "ansh-table",
445
+ `ansh-table--${props.density}`,
446
+ props.striped && "ansh-table--striped"
447
+ )
448
+ },
449
+ [
450
+ props.caption ? h("caption", { class: "ansh-visually-hidden" }, props.caption) : null,
451
+ slots.default?.()
452
+ ]
453
+ )
454
+ ]);
455
+ }
456
+ });
457
+ var Badge = defineComponent({
458
+ name: "AnshBadge",
459
+ inheritAttrs: false,
460
+ props: {
461
+ tone: { type: String, default: "neutral" },
462
+ variant: { type: String, default: "soft" },
463
+ size: { type: String, default: "md" },
464
+ /** Leading dot. Useful for statuses where the label is short. */
465
+ dot: { type: Boolean, default: false },
466
+ icon: { type: [String, Object, Function], default: void 0 }
467
+ },
468
+ setup(props, { slots, attrs }) {
469
+ return () => {
470
+ const { class: attrClass, ...restAttrs } = attrs;
471
+ return h(
472
+ "span",
473
+ {
474
+ class: cx(
475
+ "ansh-badge",
476
+ `ansh-badge--${props.tone}`,
477
+ `ansh-badge--${props.variant}`,
478
+ `ansh-badge--${props.size}`,
479
+ attrClass
480
+ ),
481
+ ...restAttrs
482
+ },
483
+ [
484
+ props.dot ? h("span", { class: "ansh-badge__dot", "aria-hidden": "true" }) : null,
485
+ props.icon ? h("span", { class: "ansh-badge__icon" }, [resolveSlotContent(props.icon)]) : null,
486
+ slots.default?.()
487
+ ]
488
+ );
489
+ };
490
+ }
491
+ });
492
+ function initials(name) {
493
+ const words = name.trim().split(/\s+/).filter(Boolean);
494
+ if (words.length === 0) return "?";
495
+ if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
496
+ return (words[0][0] + words[words.length - 1][0]).toUpperCase();
497
+ }
498
+ function hash(text) {
499
+ let value = 0;
500
+ for (let i = 0; i < text.length; i++) {
501
+ value = value * 31 + text.charCodeAt(i) | 0;
502
+ }
503
+ return Math.abs(value);
504
+ }
505
+ var Avatar = defineComponent({
506
+ name: "AnshAvatar",
507
+ inheritAttrs: false,
508
+ props: {
509
+ src: { type: String, default: void 0 },
510
+ name: { type: String, required: true },
511
+ size: { type: String, default: "md" },
512
+ shape: { type: String, default: "circle" },
513
+ status: {
514
+ type: String,
515
+ default: void 0
516
+ }
517
+ },
518
+ setup(props, { attrs }) {
519
+ const failed = ref(false);
520
+ const colors = computed(() => {
521
+ const hue = hash(props.name) % 360;
522
+ const backgroundHex = toHex(parseColor(`oklch(72% 0.13 ${hue})`));
523
+ return {
524
+ background: backgroundHex,
525
+ foreground: toHex(pickReadable(backgroundHex, "#ffffff", "#101828"))
526
+ };
527
+ });
528
+ const showImage = computed(() => Boolean(props.src) && !failed.value);
529
+ return () => {
530
+ const { class: attrClass, ...restAttrs } = attrs;
531
+ return h(
532
+ "span",
533
+ {
534
+ class: cx(
535
+ "ansh-avatar",
536
+ `ansh-avatar--${props.size}`,
537
+ `ansh-avatar--${props.shape}`,
538
+ attrClass
539
+ ),
540
+ ...restAttrs
541
+ },
542
+ [
543
+ showImage.value ? h("img", {
544
+ src: props.src,
545
+ alt: props.name,
546
+ class: "ansh-avatar__image",
547
+ // Falling back on error rather than showing a broken-image icon, which is what a
548
+ // bare `<img>` does when a CDN URL expires.
549
+ onError: () => {
550
+ failed.value = true;
551
+ }
552
+ }) : h(
553
+ "span",
554
+ {
555
+ class: "ansh-avatar__fallback",
556
+ style: { background: colors.value.background, color: colors.value.foreground },
557
+ "aria-hidden": "true"
558
+ },
559
+ initials(props.name)
560
+ ),
561
+ // The name is always available to assistive technology, even when the initials are
562
+ // shown as decorative.
563
+ !showImage.value ? h("span", { class: "ansh-visually-hidden" }, props.name) : null,
564
+ props.status ? h(
565
+ "span",
566
+ { class: cx("ansh-avatar__status", `ansh-avatar__status--${props.status}`) },
567
+ [h("span", { class: "ansh-visually-hidden" }, props.status)]
568
+ ) : null
569
+ ]
570
+ );
571
+ };
572
+ }
573
+ });
574
+ var AvatarGroup = defineComponent({
575
+ name: "AnshAvatarGroup",
576
+ inheritAttrs: false,
577
+ props: {
578
+ /** Maximum shown before collapsing into a "+N" chip. Default `4`. */
579
+ max: { type: Number, default: 4 },
580
+ size: { type: String, default: void 0 }
581
+ },
582
+ setup(props, { slots, attrs }) {
583
+ return () => {
584
+ const items = slots.default?.() ?? [];
585
+ const shown = items.slice(0, props.max);
586
+ const overflow = items.length - shown.length;
587
+ return h(
588
+ "span",
589
+ {
590
+ class: cx(
591
+ "ansh-avatar-group",
592
+ props.size && `ansh-avatar-group--${props.size}`,
593
+ attrs.class
594
+ )
595
+ },
596
+ [
597
+ shown,
598
+ overflow > 0 ? h(
599
+ "span",
600
+ {
601
+ class: cx(
602
+ "ansh-avatar",
603
+ props.size && `ansh-avatar--${props.size}`,
604
+ "ansh-avatar--circle",
605
+ "ansh-avatar--more"
606
+ )
607
+ },
608
+ [
609
+ h(
610
+ "span",
611
+ { class: "ansh-avatar__fallback", "aria-hidden": "true" },
612
+ `+${overflow}`
613
+ ),
614
+ h("span", { class: "ansh-visually-hidden" }, `and ${overflow} more`)
615
+ ]
616
+ ) : null
617
+ ]
618
+ );
619
+ };
620
+ }
621
+ });
622
+ var Kbd = defineComponent({
623
+ name: "AnshKbd",
624
+ inheritAttrs: false,
625
+ setup(_props, { slots, attrs }) {
626
+ return () => {
627
+ const nodes = slots.default?.() ?? [];
628
+ const text = nodes.map((node) => typeof node.children === "string" ? node.children : "").join("");
629
+ const keys = text.split("+").map((key) => key.trim());
630
+ return h(
631
+ "span",
632
+ { class: cx("ansh-kbd-group", attrs.class) },
633
+ keys.map(
634
+ (key, index) => h("span", { key: `${key}-${index}` }, [
635
+ index > 0 ? h("span", { class: "ansh-kbd__plus", "aria-hidden": "true" }, "+") : null,
636
+ h("kbd", { class: "ansh-kbd" }, key)
637
+ ])
638
+ )
639
+ );
640
+ };
641
+ }
642
+ });
643
+ var Stat = defineComponent({
644
+ name: "AnshStat",
645
+ inheritAttrs: false,
646
+ props: {
647
+ label: { type: [String, Object, Function], required: true },
648
+ value: { type: [String, Object, Function], required: true },
649
+ delta: { type: Number, default: void 0 },
650
+ deltaLabel: { type: String, default: void 0 },
651
+ invertDelta: { type: Boolean, default: false },
652
+ hint: { type: [String, Object, Function], default: void 0 },
653
+ icon: { type: [String, Object, Function], default: void 0 }
654
+ },
655
+ setup(props, { attrs }) {
656
+ return () => {
657
+ const delta = props.delta;
658
+ const positive = delta !== void 0 && delta > 0;
659
+ const negative = delta !== void 0 && delta < 0;
660
+ const good = props.invertDelta ? negative : positive;
661
+ return h("div", { class: cx("ansh-stat", attrs.class) }, [
662
+ h("div", { class: "ansh-stat__head" }, [
663
+ h("span", { class: "ansh-stat__label" }, [resolveSlotContent(props.label)]),
664
+ props.icon ? h("span", { class: "ansh-stat__icon" }, [resolveSlotContent(props.icon)]) : null
665
+ ]),
666
+ h("div", { class: "ansh-stat__value" }, [resolveSlotContent(props.value)]),
667
+ delta !== void 0 ? h(
668
+ "div",
669
+ {
670
+ class: cx(
671
+ "ansh-stat__delta",
672
+ delta !== 0 && (good ? "ansh-stat__delta--good" : "ansh-stat__delta--bad")
673
+ )
674
+ },
675
+ [
676
+ h(
677
+ "span",
678
+ { class: "ansh-stat__arrow", "aria-hidden": "true" },
679
+ positive ? "\u2191" : negative ? "\u2193" : "\u2192"
680
+ ),
681
+ h("span", {}, props.deltaLabel ?? `${delta > 0 ? "+" : ""}${delta}%`),
682
+ props.hint ? h("span", { class: "ansh-stat__hint" }, [resolveSlotContent(props.hint)]) : null
683
+ ]
684
+ ) : props.hint ? h("div", { class: "ansh-stat__hint" }, [resolveSlotContent(props.hint)]) : null
685
+ ]);
686
+ };
687
+ }
688
+ });
689
+ var EmptyState = defineComponent({
690
+ name: "AnshEmptyState",
691
+ inheritAttrs: false,
692
+ props: {
693
+ title: { type: [String, Object, Function], required: true },
694
+ description: { type: [String, Object, Function], default: void 0 },
695
+ icon: { type: [String, Object, Function], default: void 0 },
696
+ action: { type: [String, Object, Function], default: void 0 },
697
+ size: { type: String, default: "md" }
698
+ },
699
+ setup(props, { attrs }) {
700
+ return () => h("div", { class: cx("ansh-empty", `ansh-empty--${props.size}`, attrs.class) }, [
701
+ props.icon ? h("span", { class: "ansh-empty__icon", "aria-hidden": "true" }, [
702
+ resolveSlotContent(props.icon)
703
+ ]) : null,
704
+ h("p", { class: "ansh-empty__title" }, [resolveSlotContent(props.title)]),
705
+ props.description ? h("p", { class: "ansh-empty__description" }, [resolveSlotContent(props.description)]) : null,
706
+ props.action ? h("div", { class: "ansh-empty__action" }, [resolveSlotContent(props.action)]) : null
707
+ ]);
708
+ }
709
+ });
710
+ var DescriptionList = defineComponent({
711
+ name: "AnshDescriptionList",
712
+ inheritAttrs: false,
713
+ props: {
714
+ items: {
715
+ type: Array,
716
+ required: true
717
+ },
718
+ orientation: { type: String, default: "horizontal" }
719
+ },
720
+ setup(props, { attrs }) {
721
+ return () => h(
722
+ "dl",
723
+ { class: cx("ansh-dl", `ansh-dl--${props.orientation}`, attrs.class) },
724
+ props.items.map(
725
+ (item, index) => h("div", { class: "ansh-dl__row", key: index }, [
726
+ h("dt", { class: "ansh-dl__term" }, [resolveSlotContent(item.term)]),
727
+ h("dd", { class: "ansh-dl__description" }, [resolveSlotContent(item.description)])
728
+ ])
729
+ )
730
+ );
731
+ }
732
+ });
733
+ var Timeline = defineComponent({
734
+ name: "AnshTimeline",
735
+ inheritAttrs: false,
736
+ props: {
737
+ items: { type: Array, required: true }
738
+ },
739
+ setup(props, { attrs }) {
740
+ return () => h(
741
+ "ol",
742
+ { class: cx("ansh-timeline", attrs.class) },
743
+ props.items.map(
744
+ (item) => h(
745
+ "li",
746
+ {
747
+ key: item.id,
748
+ class: cx("ansh-timeline__item", item.tone && `ansh-timeline__item--${item.tone}`)
749
+ },
750
+ [
751
+ h("span", { class: "ansh-timeline__marker", "aria-hidden": "true" }, [
752
+ item.icon ? resolveSlotContent(item.icon) : null
753
+ ]),
754
+ h("div", { class: "ansh-timeline__body" }, [
755
+ h("div", { class: "ansh-timeline__head" }, [
756
+ h("span", { class: "ansh-timeline__title" }, [resolveSlotContent(item.title)]),
757
+ item.timestamp ? h("span", { class: "ansh-timeline__time" }, [
758
+ resolveSlotContent(item.timestamp)
759
+ ]) : null
760
+ ]),
761
+ item.description ? h("p", { class: "ansh-timeline__description" }, [
762
+ resolveSlotContent(item.description)
763
+ ]) : null
764
+ ])
765
+ ]
766
+ )
767
+ )
768
+ );
769
+ }
770
+ });
771
+ var CodeBlock = defineComponent({
772
+ name: "AnshCodeBlock",
773
+ inheritAttrs: false,
774
+ props: {
775
+ code: { type: String, required: true },
776
+ language: { type: String, default: "tsx" },
777
+ copyable: { type: Boolean, default: true },
778
+ lineNumbers: { type: Boolean, default: false },
779
+ filename: { type: String, default: void 0 }
780
+ },
781
+ setup(props, { attrs }) {
782
+ const copied = ref(false);
783
+ let timer;
784
+ const copy = async () => {
785
+ try {
786
+ await navigator.clipboard.writeText(props.code);
787
+ copied.value = true;
788
+ clearTimeout(timer);
789
+ timer = setTimeout(() => {
790
+ copied.value = false;
791
+ }, 1600);
792
+ } catch {
793
+ }
794
+ };
795
+ onBeforeUnmount(() => clearTimeout(timer));
796
+ return () => {
797
+ const lines = props.code.split("\n");
798
+ return h("div", { class: cx("ansh-code", attrs.class) }, [
799
+ props.filename || props.copyable ? h("div", { class: "ansh-code__bar" }, [
800
+ h("span", { class: "ansh-code__filename" }, props.filename ?? props.language),
801
+ props.copyable ? h(
802
+ "button",
803
+ { type: "button", class: "ansh-code__copy ansh-focusable", onClick: copy },
804
+ [
805
+ copied.value ? "Copied" : "Copy",
806
+ h("span", { class: "ansh-visually-hidden" }, " code to clipboard")
807
+ ]
808
+ ) : null
809
+ ]) : null,
810
+ h("pre", { class: cx("ansh-code__pre", props.lineNumbers && "ansh-code__pre--numbered") }, [
811
+ h(
812
+ "code",
813
+ { class: `language-${props.language}` },
814
+ props.lineNumbers ? lines.map(
815
+ (line, index) => h("span", { class: "ansh-code__line", key: index }, [
816
+ h(
817
+ "span",
818
+ { class: "ansh-code__lineno", "aria-hidden": "true" },
819
+ String(index + 1)
820
+ ),
821
+ line,
822
+ "\n"
823
+ ])
824
+ ) : props.code
825
+ )
826
+ ])
827
+ ]);
828
+ };
829
+ }
830
+ });
831
+ var Progress = defineComponent({
832
+ name: "AnshProgress",
833
+ inheritAttrs: false,
834
+ props: {
835
+ value: { type: Number, default: void 0 },
836
+ max: { type: Number, default: 100 },
837
+ size: { type: String, default: "md" },
838
+ tone: {
839
+ type: String,
840
+ default: "accent"
841
+ },
842
+ label: { type: String, required: true },
843
+ showLabel: { type: Boolean, default: false },
844
+ formatValue: {
845
+ type: Function,
846
+ default: void 0
847
+ }
848
+ },
849
+ setup(props, { attrs }) {
850
+ return () => {
851
+ const rawValue = props.value;
852
+ const indeterminate = rawValue === void 0;
853
+ const clamped = rawValue === void 0 ? 0 : Math.min(Math.max(rawValue, 0), props.max);
854
+ const percent = clamped / props.max * 100;
855
+ const text = props.formatValue ? props.formatValue(clamped, props.max) : `${Math.round(percent)}%`;
856
+ return h(
857
+ "div",
858
+ {
859
+ class: cx(
860
+ "ansh-progress",
861
+ `ansh-progress--${props.size}`,
862
+ `ansh-progress--${props.tone}`,
863
+ attrs.class
864
+ )
865
+ },
866
+ [
867
+ props.showLabel ? h("div", { class: "ansh-progress__labels" }, [
868
+ h("span", { class: "ansh-progress__label" }, props.label),
869
+ !indeterminate ? h("span", { class: "ansh-progress__value" }, text) : null
870
+ ]) : null,
871
+ h(
872
+ "div",
873
+ {
874
+ class: cx(
875
+ "ansh-progress__track",
876
+ indeterminate && "ansh-progress__track--indeterminate"
877
+ ),
878
+ role: "progressbar",
879
+ "aria-label": props.label,
880
+ // An indeterminate progressbar omits `aria-valuenow` entirely. Setting it to 0 would
881
+ // be announced as "0 percent", implying stalled work rather than unknown duration.
882
+ ...indeterminate ? null : {
883
+ "aria-valuenow": clamped,
884
+ "aria-valuemin": 0,
885
+ "aria-valuemax": props.max,
886
+ "aria-valuetext": text
887
+ }
888
+ },
889
+ [
890
+ h("div", {
891
+ class: "ansh-progress__fill",
892
+ style: indeterminate ? void 0 : { width: `${percent}%` }
893
+ })
894
+ ]
895
+ )
896
+ ]
897
+ );
898
+ };
899
+ }
900
+ });
901
+ var CircularProgress = defineComponent({
902
+ name: "AnshCircularProgress",
903
+ inheritAttrs: false,
904
+ props: {
905
+ value: { type: Number, default: void 0 },
906
+ max: { type: Number, default: 100 },
907
+ size: { type: Number, default: 48 },
908
+ thickness: { type: Number, default: 4 },
909
+ tone: {
910
+ type: String,
911
+ default: "accent"
912
+ },
913
+ label: { type: String, required: true },
914
+ formatValue: {
915
+ type: Function,
916
+ default: void 0
917
+ }
918
+ },
919
+ setup(props, { attrs, slots }) {
920
+ return () => {
921
+ const rawValue = props.value;
922
+ const indeterminate = rawValue === void 0;
923
+ const clamped = rawValue === void 0 ? 25 : Math.min(Math.max(rawValue, 0), props.max);
924
+ const fraction = clamped / props.max;
925
+ const radius = (props.size - props.thickness) / 2;
926
+ const circumference = 2 * Math.PI * radius;
927
+ const text = props.formatValue ? props.formatValue(clamped, props.max) : `${Math.round(fraction * 100)}%`;
928
+ return h(
929
+ "div",
930
+ {
931
+ class: cx(
932
+ "ansh-ring",
933
+ `ansh-ring--${props.tone}`,
934
+ indeterminate && "ansh-ring--indeterminate",
935
+ attrs.class
936
+ ),
937
+ style: { width: `${props.size}px`, height: `${props.size}px` },
938
+ role: "progressbar",
939
+ "aria-label": props.label,
940
+ ...indeterminate ? null : {
941
+ "aria-valuenow": clamped,
942
+ "aria-valuemin": 0,
943
+ "aria-valuemax": props.max,
944
+ "aria-valuetext": text
945
+ }
946
+ },
947
+ [
948
+ h(
949
+ "svg",
950
+ {
951
+ width: props.size,
952
+ height: props.size,
953
+ viewBox: `0 0 ${props.size} ${props.size}`,
954
+ "aria-hidden": "true"
955
+ },
956
+ [
957
+ h("circle", {
958
+ class: "ansh-ring__track",
959
+ cx: props.size / 2,
960
+ cy: props.size / 2,
961
+ r: radius,
962
+ fill: "none",
963
+ "stroke-width": props.thickness
964
+ }),
965
+ h("circle", {
966
+ class: "ansh-ring__fill",
967
+ cx: props.size / 2,
968
+ cy: props.size / 2,
969
+ r: radius,
970
+ fill: "none",
971
+ "stroke-width": props.thickness,
972
+ "stroke-linecap": "round",
973
+ "stroke-dasharray": circumference,
974
+ "stroke-dashoffset": circumference * (1 - fraction),
975
+ transform: `rotate(-90 ${props.size / 2} ${props.size / 2})`
976
+ })
977
+ ]
978
+ ),
979
+ slots.default ? h("span", { class: "ansh-ring__content" }, slots.default()) : null
980
+ ]
981
+ );
982
+ };
983
+ }
984
+ });
985
+ var Meter = defineComponent({
986
+ name: "AnshMeter",
987
+ inheritAttrs: false,
988
+ props: {
989
+ value: { type: Number, required: true },
990
+ min: { type: Number, default: 0 },
991
+ max: { type: Number, default: 100 },
992
+ low: { type: Number, default: void 0 },
993
+ high: { type: Number, default: void 0 },
994
+ inverted: { type: Boolean, default: false },
995
+ label: { type: String, required: true },
996
+ showValue: { type: Boolean, default: true },
997
+ formatValue: { type: Function, default: void 0 }
998
+ },
999
+ setup(props, { attrs }) {
1000
+ return () => {
1001
+ const clamped = Math.min(Math.max(props.value, props.min), props.max);
1002
+ const percent = (clamped - props.min) / (props.max - props.min) * 100;
1003
+ let tone = "success";
1004
+ if (props.low !== void 0 && clamped < props.low)
1005
+ tone = props.inverted ? "success" : "danger";
1006
+ else if (props.high !== void 0 && clamped > props.high)
1007
+ tone = props.inverted ? "danger" : "success";
1008
+ else if (props.low !== void 0 || props.high !== void 0) tone = "warning";
1009
+ const text = props.formatValue ? props.formatValue(clamped) : String(clamped);
1010
+ return h("div", { class: cx("ansh-meter", `ansh-meter--${tone}`, attrs.class) }, [
1011
+ h("div", { class: "ansh-progress__labels" }, [
1012
+ h("span", { class: "ansh-progress__label" }, props.label),
1013
+ props.showValue ? h("span", { class: "ansh-progress__value" }, text) : null
1014
+ ]),
1015
+ h(
1016
+ "div",
1017
+ {
1018
+ class: "ansh-progress__track",
1019
+ role: "meter",
1020
+ "aria-label": props.label,
1021
+ "aria-valuenow": clamped,
1022
+ "aria-valuemin": props.min,
1023
+ "aria-valuemax": props.max,
1024
+ "aria-valuetext": text
1025
+ },
1026
+ [h("div", { class: "ansh-progress__fill", style: { width: `${percent}%` } })]
1027
+ )
1028
+ ]);
1029
+ };
1030
+ }
1031
+ });
1032
+
1033
+ export { Avatar, AvatarGroup, Badge, CircularProgress, CodeBlock, DataTable, DescriptionList, EmptyState, Kbd, Meter, Pagination, Progress, Stat, Table, Timeline };
1034
+ //# sourceMappingURL=chunk-FVKLCGA4.js.map
1035
+ //# sourceMappingURL=chunk-FVKLCGA4.js.map