@exsto/ds 0.0.1-rc.13 → 0.0.1-rc.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/date-picker.js +1 -344
- package/dist/index.js +75 -4513
- package/dist/rich-text-editor.js +3 -904
- package/dist/source-hash.txt +1 -1
- package/dist/styles.css +5 -19
- package/dist/theme.css +2 -1
- package/dist/ui/src/components/edyn/animated-number/animated-number.js +108 -0
- package/dist/ui/src/components/edyn/animated-number/animated-number.js.map +1 -0
- package/dist/ui/src/components/edyn/audio-player/audio-player.js +86 -0
- package/dist/ui/src/components/edyn/audio-player/audio-player.js.map +1 -0
- package/dist/ui/src/components/edyn/date-picker/date-picker.js +250 -0
- package/dist/ui/src/components/edyn/date-picker/date-picker.js.map +1 -0
- package/dist/ui/src/components/edyn/date-picker/date-text.js +105 -0
- package/dist/ui/src/components/edyn/date-picker/date-text.js.map +1 -0
- package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js +59 -0
- package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js.map +1 -0
- package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.js +86 -0
- package/dist/ui/src/components/edyn/initials-avatar/initials-avatar.js.map +1 -0
- package/dist/ui/src/components/edyn/multi-select/multi-select.js +332 -0
- package/dist/ui/src/components/edyn/multi-select/multi-select.js.map +1 -0
- package/dist/ui/src/components/edyn/progress-line/progress-line.js +100 -0
- package/dist/ui/src/components/edyn/progress-line/progress-line.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-content/allow-list.js +37 -0
- package/dist/ui/src/components/edyn/rich-text-content/allow-list.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.js +124 -0
- package/dist/ui/src/components/edyn/rich-text-content/rich-text-content.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/extensions.js +124 -0
- package/dist/ui/src/components/edyn/rich-text-editor/extensions.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/mention-list.js +232 -0
- package/dist/ui/src/components/edyn/rich-text-editor/mention-list.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js +244 -0
- package/dist/ui/src/components/edyn/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/serialize.js +55 -0
- package/dist/ui/src/components/edyn/rich-text-editor/serialize.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/sync.js +34 -0
- package/dist/ui/src/components/edyn/rich-text-editor/sync.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js +235 -0
- package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js.map +1 -0
- package/dist/ui/src/components/edyn/rich-text-editor/types.js +26 -0
- package/dist/ui/src/components/edyn/rich-text-editor/types.js.map +1 -0
- package/dist/ui/src/components/edyn/video-player/video-player.js +132 -0
- package/dist/ui/src/components/edyn/video-player/video-player.js.map +1 -0
- package/dist/ui/src/components/ui/accordion/accordion.js +83 -0
- package/dist/ui/src/components/ui/accordion/accordion.js.map +1 -0
- package/dist/ui/src/components/ui/alert/alert.js +47 -0
- package/dist/ui/src/components/ui/alert/alert.js.map +1 -0
- package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js +95 -0
- package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/ui/src/components/ui/aspect-ratio/aspect-ratio.js +15 -0
- package/dist/ui/src/components/ui/aspect-ratio/aspect-ratio.js.map +1 -0
- package/dist/ui/src/components/ui/attachment/attachment.js +105 -0
- package/dist/ui/src/components/ui/attachment/attachment.js.map +1 -0
- package/dist/ui/src/components/ui/avatar/avatar.js +51 -0
- package/dist/ui/src/components/ui/avatar/avatar.js.map +1 -0
- package/dist/ui/src/components/ui/badge/badge.js +47 -0
- package/dist/ui/src/components/ui/badge/badge.js.map +1 -0
- package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js +73 -0
- package/dist/ui/src/components/ui/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/ui/src/components/ui/bubble/bubble.js +74 -0
- package/dist/ui/src/components/ui/bubble/bubble.js.map +1 -0
- package/dist/{chunks/input-syWxjY2U.js → ui/src/components/ui/button/button.js} +3 -82
- package/dist/ui/src/components/ui/button/button.js.map +1 -0
- package/dist/ui/src/components/ui/button-group/button-group.js +37 -0
- package/dist/ui/src/components/ui/button-group/button-group.js.map +1 -0
- package/dist/{chunks/date-range-picker-CWVcHgy2.js → ui/src/components/ui/calendar/calendar.js} +6 -100
- package/dist/ui/src/components/ui/calendar/calendar.js.map +1 -0
- package/dist/ui/src/components/ui/card/card.js +57 -0
- package/dist/ui/src/components/ui/card/card.js.map +1 -0
- package/dist/ui/src/components/ui/carousel/carousel.js +136 -0
- package/dist/ui/src/components/ui/carousel/carousel.js.map +1 -0
- package/dist/ui/src/components/ui/chart/chart.js +168 -0
- package/dist/ui/src/components/ui/chart/chart.js.map +1 -0
- package/dist/ui/src/components/ui/checkbox/checkbox.js +21 -0
- package/dist/ui/src/components/ui/checkbox/checkbox.js.map +1 -0
- package/dist/ui/src/components/ui/collapsible/collapsible.js +26 -0
- package/dist/ui/src/components/ui/collapsible/collapsible.js.map +1 -0
- package/dist/ui/src/components/ui/combobox/combobox.js +85 -0
- package/dist/ui/src/components/ui/combobox/combobox.js.map +1 -0
- package/dist/ui/src/components/ui/command/command.js +118 -0
- package/dist/ui/src/components/ui/command/command.js.map +1 -0
- package/dist/ui/src/components/ui/context-menu/context-menu.js +134 -0
- package/dist/ui/src/components/ui/context-menu/context-menu.js.map +1 -0
- package/dist/ui/src/components/ui/dialog/dialog.js +92 -0
- package/dist/ui/src/components/ui/dialog/dialog.js.map +1 -0
- package/dist/ui/src/components/ui/direction/direction.js +2 -0
- package/dist/ui/src/components/ui/drawer/drawer.js +86 -0
- package/dist/ui/src/components/ui/drawer/drawer.js.map +1 -0
- package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js +160 -0
- package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js.map +1 -0
- package/dist/ui/src/components/ui/empty/empty.js +61 -0
- package/dist/ui/src/components/ui/empty/empty.js.map +1 -0
- package/dist/ui/src/components/ui/field/field.js +36 -0
- package/dist/ui/src/components/ui/field/field.js.map +1 -0
- package/dist/ui/src/components/ui/hover-card/hover-card.js +38 -0
- package/dist/ui/src/components/ui/hover-card/hover-card.js.map +1 -0
- package/dist/ui/src/components/ui/indicator/indicator.js +79 -0
- package/dist/ui/src/components/ui/indicator/indicator.js.map +1 -0
- package/dist/ui/src/components/ui/input/input.js +16 -0
- package/dist/ui/src/components/ui/input/input.js.map +1 -0
- package/dist/ui/src/components/ui/input-group/input-group.js +77 -0
- package/dist/ui/src/components/ui/input-group/input-group.js.map +1 -0
- package/dist/ui/src/components/ui/input-otp/input-otp.js +48 -0
- package/dist/ui/src/components/ui/input-otp/input-otp.js.map +1 -0
- package/dist/ui/src/components/ui/item/item.js +122 -0
- package/dist/ui/src/components/ui/item/item.js.map +1 -0
- package/dist/ui/src/components/ui/kbd/kbd.js +21 -0
- package/dist/ui/src/components/ui/kbd/kbd.js.map +1 -0
- package/dist/ui/src/components/ui/label/label.js +14 -0
- package/dist/ui/src/components/ui/label/label.js.map +1 -0
- package/dist/ui/src/components/ui/marker/marker.js +44 -0
- package/dist/ui/src/components/ui/marker/marker.js.map +1 -0
- package/dist/ui/src/components/ui/menubar/menubar.js +137 -0
- package/dist/ui/src/components/ui/menubar/menubar.js.map +1 -0
- package/dist/ui/src/components/ui/message/message.js +50 -0
- package/dist/ui/src/components/ui/message/message.js.map +1 -0
- package/dist/ui/src/components/ui/message-scroller/message-scroller.js +61 -0
- package/dist/ui/src/components/ui/message-scroller/message-scroller.js.map +1 -0
- package/dist/ui/src/components/ui/native-select/native-select.js +39 -0
- package/dist/ui/src/components/ui/native-select/native-select.js.map +1 -0
- package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js +84 -0
- package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js.map +1 -0
- package/dist/ui/src/components/ui/pagination/pagination.js +104 -0
- package/dist/ui/src/components/ui/pagination/pagination.js.map +1 -0
- package/dist/ui/src/components/ui/popover/popover.js +56 -0
- package/dist/ui/src/components/ui/popover/popover.js.map +1 -0
- package/dist/ui/src/components/ui/progress/progress.js +70 -0
- package/dist/ui/src/components/ui/progress/progress.js.map +1 -0
- package/dist/ui/src/components/ui/questionnaire/questionnaire.js +171 -0
- package/dist/ui/src/components/ui/questionnaire/questionnaire.js.map +1 -0
- package/dist/ui/src/components/ui/radio-group/radio-group.js +29 -0
- package/dist/ui/src/components/ui/radio-group/radio-group.js.map +1 -0
- package/dist/ui/src/components/ui/resizable/resizable.js +29 -0
- package/dist/ui/src/components/ui/resizable/resizable.js.map +1 -0
- package/dist/ui/src/components/ui/scroll-area/scroll-area.js +119 -0
- package/dist/ui/src/components/ui/scroll-area/scroll-area.js.map +1 -0
- package/dist/ui/src/components/ui/select/select.js +99 -0
- package/dist/ui/src/components/ui/select/select.js.map +1 -0
- package/dist/ui/src/components/ui/separator/separator.js +30 -0
- package/dist/ui/src/components/ui/separator/separator.js.map +1 -0
- package/dist/ui/src/components/ui/sheet/sheet.js +89 -0
- package/dist/ui/src/components/ui/sheet/sheet.js.map +1 -0
- package/dist/ui/src/components/ui/sidebar/sidebar.js +383 -0
- package/dist/ui/src/components/ui/sidebar/sidebar.js.map +1 -0
- package/dist/ui/src/components/ui/skeleton/skeleton.js +14 -0
- package/dist/ui/src/components/ui/skeleton/skeleton.js.map +1 -0
- package/dist/ui/src/components/ui/slider/slider.js +49 -0
- package/dist/ui/src/components/ui/slider/slider.js.map +1 -0
- package/dist/ui/src/components/ui/spinner/spinner.js +28 -0
- package/dist/ui/src/components/ui/spinner/spinner.js.map +1 -0
- package/dist/ui/src/components/ui/switch/switch.js +20 -0
- package/dist/ui/src/components/ui/switch/switch.js.map +1 -0
- package/dist/ui/src/components/ui/table/table.js +68 -0
- package/dist/ui/src/components/ui/table/table.js.map +1 -0
- package/dist/ui/src/components/ui/tabs/tabs.js +50 -0
- package/dist/ui/src/components/ui/tabs/tabs.js.map +1 -0
- package/dist/ui/src/components/ui/textarea/textarea.js +14 -0
- package/dist/ui/src/components/ui/textarea/textarea.js.map +1 -0
- package/dist/ui/src/components/ui/toast/toast.js +124 -0
- package/dist/ui/src/components/ui/toast/toast.js.map +1 -0
- package/dist/ui/src/components/ui/toggle/toggle.js +37 -0
- package/dist/ui/src/components/ui/toggle/toggle.js.map +1 -0
- package/dist/ui/src/components/ui/toggle-group/toggle-group.js +56 -0
- package/dist/ui/src/components/ui/toggle-group/toggle-group.js.map +1 -0
- package/dist/ui/src/components/ui/tooltip/tooltip.js +42 -0
- package/dist/ui/src/components/ui/tooltip/tooltip.js.map +1 -0
- package/dist/ui/src/hooks/use-media-player.js +140 -0
- package/dist/ui/src/hooks/use-media-player.js.map +1 -0
- package/dist/ui/src/hooks/use-merged-refs.js +18 -0
- package/dist/ui/src/hooks/use-merged-refs.js.map +1 -0
- package/dist/ui/src/hooks/use-mobile.js +20 -0
- package/dist/ui/src/hooks/use-mobile.js.map +1 -0
- package/dist/ui/src/lib/first-weekday.js +34 -0
- package/dist/ui/src/lib/first-weekday.js.map +1 -0
- package/dist/ui/src/lib/popup-list.js +26 -0
- package/dist/ui/src/lib/popup-list.js.map +1 -0
- package/dist/ui/src/lib/utils.js +21 -0
- package/dist/ui/src/lib/utils.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunks/date-range-picker-CWVcHgy2.js.map +0 -1
- package/dist/chunks/input-syWxjY2U.js.map +0 -1
- package/dist/chunks/toggle-Bxg8P-61.js +0 -490
- package/dist/chunks/toggle-Bxg8P-61.js.map +0 -1
- package/dist/date-picker.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/rich-text-editor.js.map +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { dateToDay, dayToDate } from "../date-range-picker/date-range-picker.js";
|
|
2
|
+
//#region ../ui/src/components/edyn/date-picker/date-text.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reading and writing a calendar day as the text a person types.
|
|
5
|
+
*
|
|
6
|
+
* A day is the string `yyyy-mm-dd` everywhere here, never a Date: a Date is an instant, and the instant for "5 March" is a
|
|
7
|
+
* different calendar day in another time zone (local midnight serialised as UTC lands on the 4th east of Greenwich). The
|
|
8
|
+
* string is also what `<input type="date">`, JSON and a .NET `DateOnly` use. Strings of that shape sort and compare as
|
|
9
|
+
* days, so min, max and disabled days are compared as plain strings with no Date involved.
|
|
10
|
+
*/
|
|
11
|
+
var SHAPE = {
|
|
12
|
+
day: "2-digit",
|
|
13
|
+
month: "2-digit",
|
|
14
|
+
year: "numeric"
|
|
15
|
+
};
|
|
16
|
+
/** The order a locale writes day, month and year in: en-GB is day, month, year; en-US month, day, year; sv year first. */
|
|
17
|
+
var datePartsOf = (locale) => new Intl.DateTimeFormat(locale, SHAPE).formatToParts(new Date(2e3, 0, 2, 12)).flatMap((part) => part.type === "day" || part.type === "month" || part.type === "year" ? [part.type] : []);
|
|
18
|
+
/** What to type, written the locale's way: `DD/MM/YYYY` for en-GB. */
|
|
19
|
+
var datePatternOf = (locale) => new Intl.DateTimeFormat(locale, SHAPE).formatToParts(new Date(2e3, 0, 2, 12)).map((part) => {
|
|
20
|
+
if (part.type === "day") return "DD";
|
|
21
|
+
if (part.type === "month") return "MM";
|
|
22
|
+
return part.type === "year" ? "YYYY" : part.value;
|
|
23
|
+
}).join("").trim();
|
|
24
|
+
/** A day written the locale's way: `05/03/2026` for en-GB. */
|
|
25
|
+
var formatDay = (day, locale) => {
|
|
26
|
+
const date = dayToDate(day);
|
|
27
|
+
return date === void 0 ? "" : new Intl.DateTimeFormat(locale, SHAPE).format(date);
|
|
28
|
+
};
|
|
29
|
+
var isRealDay = (year, month, dayOfMonth) => {
|
|
30
|
+
if (year < 1e3 || month < 1 || month > 12 || dayOfMonth < 1) return false;
|
|
31
|
+
const date = new Date(year, month - 1, dayOfMonth, 12);
|
|
32
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === dayOfMonth;
|
|
33
|
+
};
|
|
34
|
+
var toDay = (year, month, dayOfMonth) => dateToDay(new Date(year, month - 1, dayOfMonth, 12)) ?? "";
|
|
35
|
+
/**
|
|
36
|
+
* Reads typed text as a day. The locale's order decides which number is the day; a first number of four digits is always
|
|
37
|
+
* a year (so `2026-03-05` is read the same everywhere); the separator can be anything that is not a digit, and eight
|
|
38
|
+
* digits with none (`05032026`) are split by the order. A year must have four digits: `5/3/26` is refused, not guessed.
|
|
39
|
+
*/
|
|
40
|
+
var parseDateText = (text, locale) => {
|
|
41
|
+
const trimmed = text.trim();
|
|
42
|
+
if (trimmed === "") return { kind: "empty" };
|
|
43
|
+
if (!/^[\d\s./\-,]+$/.test(trimmed)) return {
|
|
44
|
+
kind: "problem",
|
|
45
|
+
problem: "format"
|
|
46
|
+
};
|
|
47
|
+
const order = datePartsOf(locale);
|
|
48
|
+
let numbers = trimmed.match(/\d+/g) ?? [];
|
|
49
|
+
let parts = order;
|
|
50
|
+
if (numbers.length === 1 && numbers[0]?.length === 8) {
|
|
51
|
+
const digits = numbers[0];
|
|
52
|
+
numbers = order[0] === "year" ? [
|
|
53
|
+
digits.slice(0, 4),
|
|
54
|
+
digits.slice(4, 6),
|
|
55
|
+
digits.slice(6)
|
|
56
|
+
] : [
|
|
57
|
+
digits.slice(0, 2),
|
|
58
|
+
digits.slice(2, 4),
|
|
59
|
+
digits.slice(4)
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
if (numbers.length !== 3) return {
|
|
63
|
+
kind: "problem",
|
|
64
|
+
problem: "format"
|
|
65
|
+
};
|
|
66
|
+
if (numbers[0]?.length === 4) parts = [
|
|
67
|
+
"year",
|
|
68
|
+
"month",
|
|
69
|
+
"day"
|
|
70
|
+
];
|
|
71
|
+
const read = {
|
|
72
|
+
day: "",
|
|
73
|
+
month: "",
|
|
74
|
+
year: ""
|
|
75
|
+
};
|
|
76
|
+
parts.forEach((part, index) => {
|
|
77
|
+
read[part] = numbers[index] ?? "";
|
|
78
|
+
});
|
|
79
|
+
if (read.year.length !== 4 || read.day.length > 2 || read.month.length > 2) return {
|
|
80
|
+
kind: "problem",
|
|
81
|
+
problem: "format"
|
|
82
|
+
};
|
|
83
|
+
const [year, month, dayOfMonth] = [
|
|
84
|
+
Number(read.year),
|
|
85
|
+
Number(read.month),
|
|
86
|
+
Number(read.day)
|
|
87
|
+
];
|
|
88
|
+
return isRealDay(year, month, dayOfMonth) ? {
|
|
89
|
+
kind: "day",
|
|
90
|
+
day: toDay(year, month, dayOfMonth)
|
|
91
|
+
} : {
|
|
92
|
+
kind: "problem",
|
|
93
|
+
problem: "notReal"
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** Whether a day is allowed, and if not, which limit it breaks. */
|
|
97
|
+
var dayProblem = (day, { min, max, disabledDates }) => {
|
|
98
|
+
if (min !== void 0 && day < min) return "beforeMin";
|
|
99
|
+
if (max !== void 0 && day > max) return "afterMax";
|
|
100
|
+
return (typeof disabledDates === "function" ? disabledDates(day) : disabledDates?.includes(day) ?? false) ? "unavailable" : void 0;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { datePartsOf, datePatternOf, dayProblem, formatDay, parseDateText };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=date-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-text.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/date-picker/date-text.ts"],"sourcesContent":["/**\n * Reading and writing a calendar day as the text a person types.\n *\n * A day is the string `yyyy-mm-dd` everywhere here, never a Date: a Date is an instant, and the instant for \"5 March\" is a\n * different calendar day in another time zone (local midnight serialised as UTC lands on the 4th east of Greenwich). The\n * string is also what `<input type=\"date\">`, JSON and a .NET `DateOnly` use. Strings of that shape sort and compare as\n * days, so min, max and disabled days are compared as plain strings with no Date involved.\n */\n\nimport { dateToDay, dayToDate } from '@edyn/ui/components/edyn/date-range-picker';\n\nexport type TDatePart = 'day' | 'month' | 'year';\n\n/** Why a typed text is not a day the picker can use. */\nexport type TDateProblem = 'format' | 'notReal' | 'beforeMin' | 'afterMax' | 'unavailable';\n\nexport type TParsedDay = { kind: 'empty' } | { kind: 'day'; day: string } | { kind: 'problem'; problem: 'format' | 'notReal' };\n\nconst SHAPE = { day: '2-digit', month: '2-digit', year: 'numeric' } as const;\n\n/** The order a locale writes day, month and year in: en-GB is day, month, year; en-US month, day, year; sv year first. */\nexport const datePartsOf = (locale: string): TDatePart[] =>\n new Intl.DateTimeFormat(locale, SHAPE)\n .formatToParts(new Date(2000, 0, 2, 12))\n .flatMap((part) => (part.type === 'day' || part.type === 'month' || part.type === 'year' ? [part.type] : []));\n\n/** What to type, written the locale's way: `DD/MM/YYYY` for en-GB. */\nexport const datePatternOf = (locale: string): string =>\n new Intl.DateTimeFormat(locale, SHAPE)\n .formatToParts(new Date(2000, 0, 2, 12))\n .map((part) => {\n if (part.type === 'day') {\n return 'DD';\n }\n if (part.type === 'month') {\n return 'MM';\n }\n return part.type === 'year' ? 'YYYY' : part.value;\n })\n .join('')\n .trim();\n\n/** A day written the locale's way: `05/03/2026` for en-GB. */\nexport const formatDay = (day: string, locale: string): string => {\n const date = dayToDate(day);\n return date === undefined ? '' : new Intl.DateTimeFormat(locale, SHAPE).format(date);\n};\n\nconst isRealDay = (year: number, month: number, dayOfMonth: number): boolean => {\n if (year < 1000 || month < 1 || month > 12 || dayOfMonth < 1) {\n return false;\n }\n const date = new Date(year, month - 1, dayOfMonth, 12);\n return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === dayOfMonth;\n};\n\nconst toDay = (year: number, month: number, dayOfMonth: number): string => dateToDay(new Date(year, month - 1, dayOfMonth, 12)) ?? '';\n\n/**\n * Reads typed text as a day. The locale's order decides which number is the day; a first number of four digits is always\n * a year (so `2026-03-05` is read the same everywhere); the separator can be anything that is not a digit, and eight\n * digits with none (`05032026`) are split by the order. A year must have four digits: `5/3/26` is refused, not guessed.\n */\nexport const parseDateText = (text: string, locale: string): TParsedDay => {\n const trimmed = text.trim();\n if (trimmed === '') {\n return { kind: 'empty' };\n }\n if (!/^[\\d\\s./\\-,]+$/.test(trimmed)) {\n return { kind: 'problem', problem: 'format' };\n }\n const order = datePartsOf(locale);\n let numbers = trimmed.match(/\\d+/g) ?? [];\n let parts: readonly TDatePart[] = order;\n if (numbers.length === 1 && numbers[0]?.length === 8) {\n const digits = numbers[0];\n const yearFirst = order[0] === 'year';\n numbers = yearFirst ? [digits.slice(0, 4), digits.slice(4, 6), digits.slice(6)] : [digits.slice(0, 2), digits.slice(2, 4), digits.slice(4)];\n }\n if (numbers.length !== 3) {\n return { kind: 'problem', problem: 'format' };\n }\n if (numbers[0]?.length === 4) {\n parts = ['year', 'month', 'day'];\n }\n const read: Record<TDatePart, string> = { day: '', month: '', year: '' };\n parts.forEach((part, index) => {\n read[part] = numbers[index] ?? '';\n });\n if (read.year.length !== 4 || read.day.length > 2 || read.month.length > 2) {\n return { kind: 'problem', problem: 'format' };\n }\n const [year, month, dayOfMonth] = [Number(read.year), Number(read.month), Number(read.day)];\n return isRealDay(year, month, dayOfMonth) ? { kind: 'day', day: toDay(year, month, dayOfMonth) } : { kind: 'problem', problem: 'notReal' };\n};\n\nexport type TDisabledDays = readonly string[] | ((day: string) => boolean);\n\nexport interface IDayLimits {\n min?: string;\n max?: string;\n disabledDates?: TDisabledDays;\n}\n\n/** Whether a day is allowed, and if not, which limit it breaks. */\nexport const dayProblem = (day: string, { min, max, disabledDates }: IDayLimits): TDateProblem | undefined => {\n if (min !== undefined && day < min) {\n return 'beforeMin';\n }\n if (max !== undefined && day > max) {\n return 'afterMax';\n }\n const off = typeof disabledDates === 'function' ? disabledDates(day) : (disabledDates?.includes(day) ?? false);\n return off ? 'unavailable' : undefined;\n};\n"],"mappings":";;;;;;;;;;AAkBA,IAAM,QAAQ;CAAE,KAAK;CAAW,OAAO;CAAW,MAAM;AAAU;;AAGlE,IAAa,eAAe,WAC1B,IAAI,KAAK,eAAe,QAAQ,KAAK,CAAC,CACnC,cAAc,IAAI,KAAK,KAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CACvC,SAAS,SAAU,KAAK,SAAS,SAAS,KAAK,SAAS,WAAW,KAAK,SAAS,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAE;;AAGhH,IAAa,iBAAiB,WAC5B,IAAI,KAAK,eAAe,QAAQ,KAAK,CAAC,CACnC,cAAc,IAAI,KAAK,KAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CACvC,KAAK,SAAS;CACb,IAAI,KAAK,SAAS,OAChB,OAAO;CAET,IAAI,KAAK,SAAS,SAChB,OAAO;CAET,OAAO,KAAK,SAAS,SAAS,SAAS,KAAK;AAC9C,CAAC,CAAC,CACD,KAAK,EAAE,CAAC,CACR,KAAK;;AAGV,IAAa,aAAa,KAAa,WAA2B;CAChE,MAAM,OAAO,UAAU,GAAG;CAC1B,OAAO,SAAS,KAAA,IAAY,KAAK,IAAI,KAAK,eAAe,QAAQ,KAAK,CAAC,CAAC,OAAO,IAAI;AACrF;AAEA,IAAM,aAAa,MAAc,OAAe,eAAgC;CAC9E,IAAI,OAAO,OAAQ,QAAQ,KAAK,QAAQ,MAAM,aAAa,GACzD,OAAO;CAET,MAAM,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,YAAY,EAAE;CACrD,OAAO,KAAK,YAAY,MAAM,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK,KAAK,QAAQ,MAAM;AAC5F;AAEA,IAAM,SAAS,MAAc,OAAe,eAA+B,UAAU,IAAI,KAAK,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC,KAAK;;;;;;AAOnI,IAAa,iBAAiB,MAAc,WAA+B;CACzE,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,YAAY,IACd,OAAO,EAAE,MAAM,QAAQ;CAEzB,IAAI,CAAC,iBAAiB,KAAK,OAAO,GAChC,OAAO;EAAE,MAAM;EAAW,SAAS;CAAS;CAE9C,MAAM,QAAQ,YAAY,MAAM;CAChC,IAAI,UAAU,QAAQ,MAAM,MAAM,KAAK,CAAC;CACxC,IAAI,QAA8B;CAClC,IAAI,QAAQ,WAAW,KAAK,QAAQ,EAAE,EAAE,WAAW,GAAG;EACpD,MAAM,SAAS,QAAQ;EAEvB,UADkB,MAAM,OAAO,SACT;GAAC,OAAO,MAAM,GAAG,CAAC;GAAG,OAAO,MAAM,GAAG,CAAC;GAAG,OAAO,MAAM,CAAC;EAAC,IAAI;GAAC,OAAO,MAAM,GAAG,CAAC;GAAG,OAAO,MAAM,GAAG,CAAC;GAAG,OAAO,MAAM,CAAC;EAAC;CAC5I;CACA,IAAI,QAAQ,WAAW,GACrB,OAAO;EAAE,MAAM;EAAW,SAAS;CAAS;CAE9C,IAAI,QAAQ,EAAE,EAAE,WAAW,GACzB,QAAQ;EAAC;EAAQ;EAAS;CAAK;CAEjC,MAAM,OAAkC;EAAE,KAAK;EAAI,OAAO;EAAI,MAAM;CAAG;CACvE,MAAM,SAAS,MAAM,UAAU;EAC7B,KAAK,QAAQ,QAAQ,UAAU;CACjC,CAAC;CACD,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,IAAI,SAAS,KAAK,KAAK,MAAM,SAAS,GACvE,OAAO;EAAE,MAAM;EAAW,SAAS;CAAS;CAE9C,MAAM,CAAC,MAAM,OAAO,cAAc;EAAC,OAAO,KAAK,IAAI;EAAG,OAAO,KAAK,KAAK;EAAG,OAAO,KAAK,GAAG;CAAC;CAC1F,OAAO,UAAU,MAAM,OAAO,UAAU,IAAI;EAAE,MAAM;EAAO,KAAK,MAAM,MAAM,OAAO,UAAU;CAAE,IAAI;EAAE,MAAM;EAAW,SAAS;CAAU;AAC3I;;AAWA,IAAa,cAAc,KAAa,EAAE,KAAK,KAAK,oBAA0D;CAC5G,IAAI,QAAQ,KAAA,KAAa,MAAM,KAC7B,OAAO;CAET,IAAI,QAAQ,KAAA,KAAa,MAAM,KAC7B,OAAO;CAGT,QADY,OAAO,kBAAkB,aAAa,cAAc,GAAG,IAAK,eAAe,SAAS,GAAG,KAAK,SAC3F,gBAAgB,KAAA;AAC/B"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { Button } from "../../ui/button/button.js";
|
|
3
|
+
import { Calendar } from "../../ui/calendar/calendar.js";
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover/popover.js";
|
|
5
|
+
import { useIsMobile } from "../../../hooks/use-mobile.js";
|
|
6
|
+
import { firstWeekdayOf } from "../../../lib/first-weekday.js";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { IconCalendar } from "@tabler/icons-react";
|
|
9
|
+
//#region ../ui/src/components/edyn/date-range-picker/date-range-picker.tsx
|
|
10
|
+
var dayToDate = (value) => value === void 0 ? void 0 : /* @__PURE__ */ new Date(`${value}T12:00:00`);
|
|
11
|
+
var dateToDay = (date) => date === void 0 ? void 0 : `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
12
|
+
/** A range of calendar days, either end optional; two months side by side, one on a phone. */
|
|
13
|
+
var DateRangePicker = ({ id, value, onValueChange, placeholder = "Select date range", locale = "en-GB", summary = "range", "aria-label": ariaLabel, className, ...props }) => {
|
|
14
|
+
const isMobile = useIsMobile();
|
|
15
|
+
const from = dayToDate(value.from);
|
|
16
|
+
const to = dayToDate(value.to);
|
|
17
|
+
const format = new Intl.DateTimeFormat(locale, {
|
|
18
|
+
day: "2-digit",
|
|
19
|
+
month: "short",
|
|
20
|
+
year: "numeric"
|
|
21
|
+
});
|
|
22
|
+
const ends = [from, to].filter((date) => date !== void 0).map((date) => format.format(date));
|
|
23
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
|
|
24
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
25
|
+
...props,
|
|
26
|
+
id,
|
|
27
|
+
"aria-label": ariaLabel,
|
|
28
|
+
"data-slot": "date-range-picker-trigger",
|
|
29
|
+
"data-set": ends.length > 0 ? "" : void 0,
|
|
30
|
+
variant: "outline",
|
|
31
|
+
className: cn("w-full min-w-0 justify-start font-normal", ends.length === 0 && "text-muted-foreground", className)
|
|
32
|
+
}),
|
|
33
|
+
children: [/* @__PURE__ */ jsx(IconCalendar, { "aria-hidden": "true" }), /* @__PURE__ */ jsx("span", {
|
|
34
|
+
className: "truncate",
|
|
35
|
+
children: ends.length === 0 || summary === "label" ? placeholder : ends.join(" – ")
|
|
36
|
+
})]
|
|
37
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
38
|
+
align: "start",
|
|
39
|
+
className: "w-auto p-0",
|
|
40
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
41
|
+
mode: "range",
|
|
42
|
+
defaultMonth: from,
|
|
43
|
+
numberOfMonths: isMobile ? 1 : 2,
|
|
44
|
+
weekStartsOn: firstWeekdayOf(locale),
|
|
45
|
+
selected: {
|
|
46
|
+
from,
|
|
47
|
+
to
|
|
48
|
+
},
|
|
49
|
+
onSelect: (range) => onValueChange({
|
|
50
|
+
from: dateToDay(range?.from),
|
|
51
|
+
to: dateToDay(range?.to)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
})] });
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
export { DateRangePicker, dateToDay, dayToDate };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=date-range-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-range-picker.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/date-range-picker/date-range-picker.tsx"],"sourcesContent":["import { Button } from '@edyn/ui/components/ui/button';\nimport { Calendar } from '@edyn/ui/components/ui/calendar';\nimport { Popover, PopoverContent, PopoverTrigger } from '@edyn/ui/components/ui/popover';\nimport { useIsMobile } from '@edyn/ui/hooks/use-mobile';\nimport { firstWeekdayOf } from '@edyn/ui/lib/first-weekday';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCalendar } from '@tabler/icons-react';\nimport type { ComponentPropsWithoutRef } from 'react';\n\n/** A range of calendar days as yyyy-mm-dd, either end optional. */\nexport type TDateRange = { from?: string; to?: string };\n\n// A calendar day is read and written at midday local time, so no time zone or daylight\n// saving change can move it onto the day before or after.\nexport const dayToDate = (value: string | undefined): Date | undefined => (value === undefined ? undefined : new Date(`${value}T12:00:00`));\n\nexport const dateToDay = (date: Date | undefined): string | undefined =>\n date === undefined ? undefined : `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;\n\n/** Anything else a button takes (data-*, aria-*, title) goes to the trigger. */\nexport interface IDateRangePickerProps\n extends Omit<ComponentPropsWithoutRef<'button'>, 'value' | 'defaultValue' | 'placeholder' | 'id' | 'className' | 'children' | 'onChange' | 'type'> {\n id?: string;\n value: TDateRange;\n onValueChange: (value: TDateRange) => void;\n placeholder?: string;\n /**\n * What the trigger says once a range is set. `range` (default) writes the ends; `label` keeps the label, so a\n * range the page shows elsewhere (as a removable chip, say) is not repeated here. In both, the trigger carries\n * `data-set` while either end is set, for a look of its own.\n */\n summary?: 'range' | 'label';\n /** How the ends are written on the trigger, and which weekday the calendar starts on (BCP 47); en-GB reads 05 Mar 2026 and starts on Monday. */\n locale?: string;\n /** An accessible name for the trigger, when there is no visible label to point at. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/** A range of calendar days, either end optional; two months side by side, one on a phone. */\nexport const DateRangePicker = ({\n id,\n value,\n onValueChange,\n placeholder = 'Select date range',\n locale = 'en-GB',\n summary = 'range',\n 'aria-label': ariaLabel,\n className,\n ...props\n}: IDateRangePickerProps) => {\n const isMobile = useIsMobile();\n const from = dayToDate(value.from);\n const to = dayToDate(value.to);\n const format = new Intl.DateTimeFormat(locale, { day: '2-digit', month: 'short', year: 'numeric' });\n const ends = [from, to].filter((date) => date !== undefined).map((date) => format.format(date));\n return (\n <Popover>\n <PopoverTrigger\n render={\n <Button\n {...props}\n id={id}\n aria-label={ariaLabel}\n data-slot=\"date-range-picker-trigger\"\n data-set={ends.length > 0 ? '' : undefined}\n variant=\"outline\"\n className={cn('w-full min-w-0 justify-start font-normal', ends.length === 0 && 'text-muted-foreground', className)}\n />\n }\n >\n <IconCalendar aria-hidden=\"true\" />\n <span className=\"truncate\">{ends.length === 0 || summary === 'label' ? placeholder : ends.join(' – ')}</span>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-auto p-0\">\n <Calendar\n mode=\"range\"\n defaultMonth={from}\n numberOfMonths={isMobile ? 1 : 2}\n weekStartsOn={firstWeekdayOf(locale)}\n selected={{ from, to }}\n onSelect={(range) => onValueChange({ from: dateToDay(range?.from), to: dateToDay(range?.to) })}\n />\n </PopoverContent>\n </Popover>\n );\n};\n"],"mappings":";;;;;;;;;AAcA,IAAa,aAAa,UAAiD,UAAU,KAAA,IAAY,KAAA,oBAAY,IAAI,KAAK,GAAG,MAAM,UAAU;AAEzI,IAAa,aAAa,SACxB,SAAS,KAAA,IAAY,KAAA,IAAY,GAAG,KAAK,YAAY,EAAE,GAAG,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;;AAuBlJ,IAAa,mBAAmB,EAC9B,IACA,OACA,eACA,cAAc,qBACd,SAAS,SACT,UAAU,SACV,cAAc,WACd,WACA,GAAG,YACwB;CAC3B,MAAM,WAAW,YAAY;CAC7B,MAAM,OAAO,UAAU,MAAM,IAAI;CACjC,MAAM,KAAK,UAAU,MAAM,EAAE;CAC7B,MAAM,SAAS,IAAI,KAAK,eAAe,QAAQ;EAAE,KAAK;EAAW,OAAO;EAAS,MAAM;CAAU,CAAC;CAClG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,SAAS,SAAS,KAAA,CAAS,CAAC,CAAC,KAAK,SAAS,OAAO,OAAO,IAAI,CAAC;CAC9F,OACE,qBAAC,SAAD,EAAA,UAAA,CACE,qBAAC,gBAAD;EACE,QACE,oBAAC,QAAD;GACE,GAAI;GACA;GACJ,cAAY;GACZ,aAAU;GACV,YAAU,KAAK,SAAS,IAAI,KAAK,KAAA;GACjC,SAAQ;GACR,WAAW,GAAG,4CAA4C,KAAK,WAAW,KAAK,yBAAyB,SAAS;EAClH,CAAA;EAVL,UAAA,CAaE,oBAAC,cAAD,EAAc,eAAY,OAAQ,CAAA,GAClC,oBAAC,QAAD;GAAM,WAAU;GAAY,UAAA,KAAK,WAAW,KAAK,YAAY,UAAU,cAAc,KAAK,KAAK,KAAK;EAAQ,CAAA,CAC9F;CAChB,CAAA,GAAA,oBAAC,gBAAD;EAAgB,OAAM;EAAQ,WAAU;EACtC,UAAA,oBAAC,UAAD;GACE,MAAK;GACL,cAAc;GACd,gBAAgB,WAAW,IAAI;GAC/B,cAAc,eAAe,MAAM;GACnC,UAAU;IAAE;IAAM;GAAG;GACrB,WAAW,UAAU,cAAc;IAAE,MAAM,UAAU,OAAO,IAAI;IAAG,IAAI,UAAU,OAAO,EAAE;GAAE,CAAC;EAC9F,CAAA;CACa,CAAA,CACT,EAAA,CAAA;AAEb"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar/avatar.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region ../ui/src/components/edyn/initials-avatar/initials-avatar.tsx
|
|
5
|
+
/** How many colour pairs the theme defines (--avatar-1-bg, --avatar-1-fg ... in @edyn/theme tokens.css). */
|
|
6
|
+
var AVATAR_TONES = 12;
|
|
7
|
+
/**
|
|
8
|
+
* The palette entry the text gets, from 1: the same text always gets the same one.
|
|
9
|
+
* FNV-1a, because a plain multiply-and-add hash puts two letters on a few entries
|
|
10
|
+
* only (it reduces to 7 x first + second, modulo 12).
|
|
11
|
+
*/
|
|
12
|
+
var avatarTone = (text) => {
|
|
13
|
+
let hash = 2166136261;
|
|
14
|
+
for (const character of text) {
|
|
15
|
+
hash ^= character.codePointAt(0) ?? 0;
|
|
16
|
+
hash = Math.imul(hash, 16777619);
|
|
17
|
+
}
|
|
18
|
+
hash ^= hash >>> 16;
|
|
19
|
+
hash = Math.imul(hash, 2246822507);
|
|
20
|
+
hash ^= hash >>> 13;
|
|
21
|
+
hash = Math.imul(hash, 3266489909);
|
|
22
|
+
hash ^= hash >>> 16;
|
|
23
|
+
return (hash >>> 0) % 12 + 1;
|
|
24
|
+
};
|
|
25
|
+
/** Up to two initials, upper-cased: the first letter of the first two words. */
|
|
26
|
+
var initialsOf = (name) => name.split(/\s+/).filter((part) => part !== "").slice(0, 2).map((part) => Array.from(part)[0]?.toUpperCase() ?? "").join("");
|
|
27
|
+
/**
|
|
28
|
+
* The circle and its letters together, so the initials always fit. The font size
|
|
29
|
+
* is inline rather than a class on purpose: 9px and 15px are not on Tailwind's
|
|
30
|
+
* type scale, a custom `text-*` token would be read by tailwind-merge as a colour
|
|
31
|
+
* and could drop the wrong class, and an inline size cannot be fought by the
|
|
32
|
+
* fallback's own `text-sm`.
|
|
33
|
+
*/
|
|
34
|
+
var SIZES = {
|
|
35
|
+
xs: {
|
|
36
|
+
box: "size-5",
|
|
37
|
+
fontSize: 9
|
|
38
|
+
},
|
|
39
|
+
sm: {
|
|
40
|
+
box: "size-6",
|
|
41
|
+
fontSize: 10
|
|
42
|
+
},
|
|
43
|
+
md: {
|
|
44
|
+
box: "size-8",
|
|
45
|
+
fontSize: 12
|
|
46
|
+
},
|
|
47
|
+
lg: {
|
|
48
|
+
box: "size-10",
|
|
49
|
+
fontSize: 15
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** A person's photo, or their initials on a circle in the palette colour they pick; both themes switch by themselves. */
|
|
53
|
+
var InitialsAvatar = ({ name, photoUrl, size = "md", className, autoColor = true, label, "aria-label": ariaLabel, ...props }) => {
|
|
54
|
+
const initials = initialsOf(name);
|
|
55
|
+
const tone = avatarTone(initials);
|
|
56
|
+
const { box, fontSize } = SIZES[size];
|
|
57
|
+
const style = autoColor ? {
|
|
58
|
+
fontSize,
|
|
59
|
+
backgroundColor: `var(--avatar-${tone}-bg)`,
|
|
60
|
+
color: `var(--avatar-${tone}-fg)`
|
|
61
|
+
} : { fontSize };
|
|
62
|
+
const accessibleName = label ?? ariaLabel;
|
|
63
|
+
const a11y = accessibleName === void 0 ? { "aria-hidden": true } : {
|
|
64
|
+
role: "img",
|
|
65
|
+
"aria-label": accessibleName
|
|
66
|
+
};
|
|
67
|
+
return /* @__PURE__ */ jsxs(Avatar, {
|
|
68
|
+
"data-slot": "initials-avatar",
|
|
69
|
+
...props,
|
|
70
|
+
"data-tone": autoColor ? tone : void 0,
|
|
71
|
+
className: cn(box, className),
|
|
72
|
+
...a11y,
|
|
73
|
+
children: [photoUrl != null && /* @__PURE__ */ jsx(AvatarImage, {
|
|
74
|
+
src: photoUrl,
|
|
75
|
+
alt: ""
|
|
76
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
77
|
+
style,
|
|
78
|
+
className: "leading-none font-semibold tracking-tight",
|
|
79
|
+
children: initials
|
|
80
|
+
})]
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { AVATAR_TONES, InitialsAvatar, avatarTone, initialsOf };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=initials-avatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initials-avatar.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/initials-avatar/initials-avatar.tsx"],"sourcesContent":["import { Avatar, AvatarFallback, AvatarImage } from '@edyn/ui/components/ui/avatar';\nimport { cn } from '@edyn/ui/lib/utils';\nimport type { ComponentPropsWithoutRef, CSSProperties } from 'react';\n\n/** How many colour pairs the theme defines (--avatar-1-bg, --avatar-1-fg ... in @edyn/theme tokens.css). */\nexport const AVATAR_TONES = 12;\n\n/**\n * The palette entry the text gets, from 1: the same text always gets the same one.\n * FNV-1a, because a plain multiply-and-add hash puts two letters on a few entries\n * only (it reduces to 7 x first + second, modulo 12).\n */\nexport const avatarTone = (text: string): number => {\n let hash = 2166136261;\n for (const character of text) {\n hash ^= character.codePointAt(0) ?? 0;\n hash = Math.imul(hash, 16777619);\n }\n // murmur3's finaliser mixes every bit into the low ones the modulo keeps; without\n // it AB, CD, EF share an entry.\n hash ^= hash >>> 16;\n hash = Math.imul(hash, 0x85ebca6b);\n hash ^= hash >>> 13;\n hash = Math.imul(hash, 0xc2b2ae35);\n hash ^= hash >>> 16;\n return ((hash >>> 0) % AVATAR_TONES) + 1;\n};\n\n/** Up to two initials, upper-cased: the first letter of the first two words. */\nexport const initialsOf = (name: string): string =>\n name\n .split(/\\s+/)\n .filter((part) => part !== '')\n .slice(0, 2)\n .map((part) => Array.from(part)[0]?.toUpperCase() ?? '')\n .join('');\n\n/**\n * The circle and its letters together, so the initials always fit. The font size\n * is inline rather than a class on purpose: 9px and 15px are not on Tailwind's\n * type scale, a custom `text-*` token would be read by tailwind-merge as a colour\n * and could drop the wrong class, and an inline size cannot be fought by the\n * fallback's own `text-sm`.\n */\nconst SIZES = {\n xs: { box: 'size-5', fontSize: 9 },\n sm: { box: 'size-6', fontSize: 10 },\n md: { box: 'size-8', fontSize: 12 },\n lg: { box: 'size-10', fontSize: 15 },\n} as const;\n\nexport type TInitialsAvatarSize = keyof typeof SIZES;\n\nexport interface IInitialsAvatarProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className' | 'role'> {\n name: string;\n photoUrl?: string | null;\n /** xs 20px, sm 24px in table rows, md 32px in a sidebar, lg 40px in a menu's header. */\n size?: TInitialsAvatarSize;\n className?: string;\n /** Colour the initials from the palette (on by default); off, they take the muted look. */\n autoColor?: boolean;\n /**\n * An accessible name. Left out, the avatar is decoration, which is right beside\n * the person's name; give one when it stands alone.\n */\n label?: string;\n}\n\n/** A person's photo, or their initials on a circle in the palette colour they pick; both themes switch by themselves. */\nexport const InitialsAvatar = ({\n name,\n photoUrl,\n size = 'md',\n className,\n autoColor = true,\n label,\n 'aria-label': ariaLabel,\n ...props\n}: IInitialsAvatarProps) => {\n const initials = initialsOf(name);\n const tone = avatarTone(initials);\n const { box, fontSize } = SIZES[size];\n const style: CSSProperties = autoColor\n ? { fontSize, backgroundColor: `var(--avatar-${tone}-bg)`, color: `var(--avatar-${tone}-fg)` }\n : { fontSize };\n const accessibleName = label ?? ariaLabel;\n const a11y = accessibleName === undefined ? ({ 'aria-hidden': true } as const) : ({ role: 'img', 'aria-label': accessibleName } as const);\n return (\n <Avatar data-slot=\"initials-avatar\" {...props} data-tone={autoColor ? tone : undefined} className={cn(box, className)} {...a11y}>\n {photoUrl != null && <AvatarImage src={photoUrl} alt=\"\" />}\n <AvatarFallback style={style} className=\"leading-none font-semibold tracking-tight\">\n {initials}\n </AvatarFallback>\n </Avatar>\n );\n};\n"],"mappings":";;;;;AAKA,IAAa,eAAe;;;;;;AAO5B,IAAa,cAAc,SAAyB;CAClD,IAAI,OAAO;CACX,KAAK,MAAM,aAAa,MAAM;EAC5B,QAAQ,UAAU,YAAY,CAAC,KAAK;EACpC,OAAO,KAAK,KAAK,MAAM,QAAQ;CACjC;CAGA,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAU;CACjC,QAAQ,SAAS;CACjB,OAAO,KAAK,KAAK,MAAM,UAAU;CACjC,QAAQ,SAAS;CACjB,QAAS,SAAS,KAAA,KAAqB;AACzC;;AAGA,IAAa,cAAc,SACzB,KACG,MAAM,KAAK,CAAC,CACZ,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,YAAY,KAAK,EAAE,CAAC,CACvD,KAAK,EAAE;;;;;;;;AASZ,IAAM,QAAQ;CACZ,IAAI;EAAE,KAAK;EAAU,UAAU;CAAE;CACjC,IAAI;EAAE,KAAK;EAAU,UAAU;CAAG;CAClC,IAAI;EAAE,KAAK;EAAU,UAAU;CAAG;CAClC,IAAI;EAAE,KAAK;EAAW,UAAU;CAAG;AACrC;;AAoBA,IAAa,kBAAkB,EAC7B,MACA,UACA,OAAO,MACP,WACA,YAAY,MACZ,OACA,cAAc,WACd,GAAG,YACuB;CAC1B,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,OAAO,WAAW,QAAQ;CAChC,MAAM,EAAE,KAAK,aAAa,MAAM;CAChC,MAAM,QAAuB,YACzB;EAAE;EAAU,iBAAiB,gBAAgB,KAAK;EAAO,OAAO,gBAAgB,KAAK;CAAM,IAC3F,EAAE,SAAS;CACf,MAAM,iBAAiB,SAAS;CAChC,MAAM,OAAO,mBAAmB,KAAA,IAAa,EAAE,eAAe,KAAK,IAAe;EAAE,MAAM;EAAO,cAAc;CAAe;CAC9H,OACE,qBAAC,QAAD;EAAQ,aAAU;EAAkB,GAAI;EAAO,aAAW,YAAY,OAAO,KAAA;EAAW,WAAW,GAAG,KAAK,SAAS;EAAG,GAAI;EAA3H,UAAA,CACG,YAAY,QAAQ,oBAAC,aAAD;GAAa,KAAK;GAAU,KAAI;EAAI,CAAA,GACzD,oBAAC,gBAAD;GAAuB;GAAO,WAAU;GACrC,UAAA;EACa,CAAA,CACV;;AAEZ"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { ScrollArea } from "../../ui/scroll-area/scroll-area.js";
|
|
3
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip/tooltip.js";
|
|
4
|
+
import { popupItemClass } from "../../../lib/popup-list.js";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { IconCheck, IconChevronDown, IconSearch } from "@tabler/icons-react";
|
|
8
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
9
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
10
|
+
//#region ../ui/src/components/edyn/multi-select/multi-select.tsx
|
|
11
|
+
var DEFAULT_MULTI_SELECT_LABELS = {
|
|
12
|
+
noMatches: "No matches.",
|
|
13
|
+
loading: "Loading…",
|
|
14
|
+
error: "Could not load the options.",
|
|
15
|
+
retry: "Try again",
|
|
16
|
+
loadMore: "Load more",
|
|
17
|
+
loadingMore: "Loading…",
|
|
18
|
+
clear: "Clear",
|
|
19
|
+
selectedCount: (count) => `${count} selected`
|
|
20
|
+
};
|
|
21
|
+
/** Above this many options a list draws only the rows on screen. */
|
|
22
|
+
var VIRTUALIZE_FROM = 100;
|
|
23
|
+
var ITEM_HEIGHT = 32;
|
|
24
|
+
/**
|
|
25
|
+
* The options behind the chosen values, in the order they were chosen. A value with no
|
|
26
|
+
* known option is kept and shown as itself rather than dropped: a selection must never
|
|
27
|
+
* change just because a list did.
|
|
28
|
+
*/
|
|
29
|
+
var resolveChosen = (value, options, selectedOptions = []) => {
|
|
30
|
+
const known = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const option of [...selectedOptions, ...options]) known.set(option.value, option);
|
|
32
|
+
return value.map((entry) => known.get(entry) ?? {
|
|
33
|
+
value: entry,
|
|
34
|
+
label: entry
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
/** What the trigger says: the placeholder, the one picked, or a count. */
|
|
38
|
+
var summarize = (chosen, placeholder, labels, summary = "selection") => {
|
|
39
|
+
if (chosen.length === 0) return placeholder;
|
|
40
|
+
if (summary === "label") return labels.selectedCount(chosen.length);
|
|
41
|
+
return chosen.length === 1 ? chosen[0]?.label ?? "" : labels.selectedCount(chosen.length);
|
|
42
|
+
};
|
|
43
|
+
/** Every chosen name, for a tooltip on a trigger whose text is cut. With `label` the names are not shown, so it is the text itself. */
|
|
44
|
+
var fullSelection = (chosen, placeholder, labels, summary = "selection") => summary === "selection" && chosen.length > 0 ? chosen.map((option) => option.label).join(", ") : summarize(chosen, placeholder, labels, summary);
|
|
45
|
+
var POPUP = "flex max-h-(--available-height) w-(--anchor-width) min-w-64 max-w-[max(24rem,var(--anchor-width))] origin-(--transform-origin) flex-col overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md outline-none duration-100 motion-reduce:animate-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95";
|
|
46
|
+
var TRIGGER = "flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm text-foreground transition-colors outline-none focus-visible:border-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive data-popup-open:border-ring dark:bg-input/30";
|
|
47
|
+
var Option = ({ option, index, style, measure, highlighted }) => {
|
|
48
|
+
const [cut, setCut] = useState(false);
|
|
49
|
+
const measureLabel = useCallback((element) => {
|
|
50
|
+
if (!element) return;
|
|
51
|
+
const measureCut = () => setCut(element.scrollWidth > element.clientWidth);
|
|
52
|
+
measureCut();
|
|
53
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
54
|
+
const observer = new ResizeObserver(measureCut);
|
|
55
|
+
observer.observe(element);
|
|
56
|
+
return () => observer.disconnect();
|
|
57
|
+
}, []);
|
|
58
|
+
return /* @__PURE__ */ jsxs(Combobox.Item, {
|
|
59
|
+
value: option,
|
|
60
|
+
index,
|
|
61
|
+
ref: measure,
|
|
62
|
+
"data-index": index,
|
|
63
|
+
style,
|
|
64
|
+
"data-slot": "multi-select-item",
|
|
65
|
+
className: cn(popupItemClass("base-ui"), "group"),
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx("span", {
|
|
68
|
+
"data-slot": "multi-select-item-indicator",
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
className: "flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground transition-colors group-data-[selected]:border-primary group-data-[selected]:bg-primary",
|
|
71
|
+
children: /* @__PURE__ */ jsx(IconCheck, {
|
|
72
|
+
className: "size-3 opacity-0 group-data-[selected]:opacity-100",
|
|
73
|
+
stroke: 3
|
|
74
|
+
})
|
|
75
|
+
}),
|
|
76
|
+
option.leading,
|
|
77
|
+
/* @__PURE__ */ jsxs(Tooltip, {
|
|
78
|
+
open: highlighted && cut,
|
|
79
|
+
children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
80
|
+
"data-slot": "multi-select-item-label",
|
|
81
|
+
"data-overflowing": cut ? "" : void 0,
|
|
82
|
+
render: /* @__PURE__ */ jsx("span", {
|
|
83
|
+
ref: measureLabel,
|
|
84
|
+
className: "min-w-0 flex-1 truncate"
|
|
85
|
+
}),
|
|
86
|
+
children: option.label
|
|
87
|
+
}, option.label), /* @__PURE__ */ jsx(TooltipContent, {
|
|
88
|
+
side: "right",
|
|
89
|
+
children: option.label
|
|
90
|
+
})]
|
|
91
|
+
}),
|
|
92
|
+
option.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
93
|
+
className: "text-xs text-muted-foreground tabular-nums",
|
|
94
|
+
children: option.count
|
|
95
|
+
})
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
var Options = ({ scroller, scrollTo, busy, highlightedValue }) => {
|
|
100
|
+
const items = Combobox.useFilteredItems();
|
|
101
|
+
const virtual = items.length > 100;
|
|
102
|
+
const [mounted, setMounted] = useState(false);
|
|
103
|
+
useEffect(() => setMounted(true), []);
|
|
104
|
+
const virtualizer = useVirtualizer({
|
|
105
|
+
count: items.length,
|
|
106
|
+
getScrollElement: () => scroller.current,
|
|
107
|
+
estimateSize: () => ITEM_HEIGHT,
|
|
108
|
+
overscan: 12,
|
|
109
|
+
enabled: virtual && mounted
|
|
110
|
+
});
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
scrollTo.current = virtual ? (index) => virtualizer.scrollToIndex(index, { align: "auto" }) : () => void 0;
|
|
113
|
+
}, [
|
|
114
|
+
scrollTo,
|
|
115
|
+
virtual,
|
|
116
|
+
virtualizer
|
|
117
|
+
]);
|
|
118
|
+
if (!virtual) return /* @__PURE__ */ jsx(Combobox.List, {
|
|
119
|
+
"aria-busy": busy || void 0,
|
|
120
|
+
className: "p-1 outline-none",
|
|
121
|
+
children: items.map((option, index) => /* @__PURE__ */ jsx(Option, {
|
|
122
|
+
option,
|
|
123
|
+
index,
|
|
124
|
+
highlighted: highlightedValue === option.value
|
|
125
|
+
}, option.value))
|
|
126
|
+
});
|
|
127
|
+
return /* @__PURE__ */ jsx(Combobox.List, {
|
|
128
|
+
"data-virtualized": "",
|
|
129
|
+
"aria-busy": busy || void 0,
|
|
130
|
+
className: "relative m-1 outline-none",
|
|
131
|
+
style: { height: virtualizer.getTotalSize() },
|
|
132
|
+
children: virtualizer.getVirtualItems().map((row) => {
|
|
133
|
+
const option = items[row.index];
|
|
134
|
+
return option === void 0 ? null : /* @__PURE__ */ jsx(Option, {
|
|
135
|
+
option,
|
|
136
|
+
index: row.index,
|
|
137
|
+
highlighted: highlightedValue === option.value,
|
|
138
|
+
measure: virtualizer.measureElement,
|
|
139
|
+
style: {
|
|
140
|
+
position: "absolute",
|
|
141
|
+
top: 0,
|
|
142
|
+
left: 0,
|
|
143
|
+
right: 0,
|
|
144
|
+
transform: `translateY(${row.start}px)`
|
|
145
|
+
}
|
|
146
|
+
}, option.value);
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* A choice of several from a list: a trigger shaped like a Select that says what is picked
|
|
152
|
+
* (the placeholder, the one picked, or a count), and a popup with a search field and checkbox
|
|
153
|
+
* options, with the shared popup list style, the ScrollArea's bar and fades, and virtualization
|
|
154
|
+
* past VIRTUALIZE_FROM options. Nothing chosen means no filter.
|
|
155
|
+
*
|
|
156
|
+
* For a short fixed list pass `options` and nothing else: the component filters them as the
|
|
157
|
+
* user types. For a list that grows, pass `onSearchChange` and it becomes a window onto a
|
|
158
|
+
* server: the caller fetches the options for the typed text, and says when they are loading,
|
|
159
|
+
* failed, or have another page.
|
|
160
|
+
*/
|
|
161
|
+
var MultiSelect = ({ placeholder, searchPlaceholder, options, value, onValueChange, id, disabled = false, className, onSearchChange, searchDelay = 250, selectedOptions, loading = false, error = false, onRetry, hasMore = false, onLoadMore, loadingMore = false, labels: labelOverrides, summary = "selection", ...rest }) => {
|
|
162
|
+
const labels = useMemo(() => ({
|
|
163
|
+
...DEFAULT_MULTI_SELECT_LABELS,
|
|
164
|
+
...labelOverrides
|
|
165
|
+
}), [labelOverrides]);
|
|
166
|
+
const isAsync = onSearchChange !== void 0;
|
|
167
|
+
const chosen = useMemo(() => resolveChosen(value, options, selectedOptions), [
|
|
168
|
+
value,
|
|
169
|
+
options,
|
|
170
|
+
selectedOptions
|
|
171
|
+
]);
|
|
172
|
+
const items = useMemo(() => {
|
|
173
|
+
const offered = new Set(options.map((option) => option.value));
|
|
174
|
+
return [...chosen.filter((option) => !offered.has(option.value)), ...options];
|
|
175
|
+
}, [chosen, options]);
|
|
176
|
+
const refScroller = useRef(null);
|
|
177
|
+
const refScrollTo = useRef(() => void 0);
|
|
178
|
+
const refTimer = useRef(void 0);
|
|
179
|
+
const refTriggerText = useRef(null);
|
|
180
|
+
const refSearch = useRef(null);
|
|
181
|
+
const [popupOpen, setPopupOpen] = useState(false);
|
|
182
|
+
const [highlightedValue, setHighlightedValue] = useState(null);
|
|
183
|
+
const refReported = useRef(void 0);
|
|
184
|
+
const refOpen = useRef(false);
|
|
185
|
+
const errorMessage = typeof error === "string" ? error : labels.error;
|
|
186
|
+
const failed = error !== false;
|
|
187
|
+
useEffect(() => () => window.clearTimeout(refTimer.current), []);
|
|
188
|
+
const closeFromTrigger = (event) => {
|
|
189
|
+
if (event.key !== "Escape" || !popupOpen || refSearch.current === null) return;
|
|
190
|
+
event.stopPropagation();
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
refSearch.current.dispatchEvent(new KeyboardEvent("keydown", {
|
|
193
|
+
key: "Escape",
|
|
194
|
+
bubbles: true,
|
|
195
|
+
cancelable: true
|
|
196
|
+
}));
|
|
197
|
+
};
|
|
198
|
+
return /* @__PURE__ */ jsxs(Combobox.Root, {
|
|
199
|
+
multiple: true,
|
|
200
|
+
disabled,
|
|
201
|
+
items,
|
|
202
|
+
filter: isAsync ? null : void 0,
|
|
203
|
+
value: chosen,
|
|
204
|
+
onValueChange: (next) => onValueChange(next.map((option) => option.value)),
|
|
205
|
+
onInputValueChange: (text) => {
|
|
206
|
+
if (onSearchChange === void 0 || !refOpen.current) return;
|
|
207
|
+
window.clearTimeout(refTimer.current);
|
|
208
|
+
if (text === refReported.current) return;
|
|
209
|
+
refTimer.current = window.setTimeout(() => {
|
|
210
|
+
refReported.current = text;
|
|
211
|
+
onSearchChange(text);
|
|
212
|
+
}, searchDelay);
|
|
213
|
+
},
|
|
214
|
+
onOpenChange: (open) => {
|
|
215
|
+
window.clearTimeout(refTimer.current);
|
|
216
|
+
setPopupOpen(open);
|
|
217
|
+
if (!open) setHighlightedValue(null);
|
|
218
|
+
refOpen.current = open;
|
|
219
|
+
refReported.current = open ? "" : void 0;
|
|
220
|
+
if (open && onSearchChange !== void 0) onSearchChange("");
|
|
221
|
+
},
|
|
222
|
+
itemToStringLabel: (option) => option.label,
|
|
223
|
+
isItemEqualToValue: (a, b) => a.value === b.value,
|
|
224
|
+
virtualized: items.length > 100,
|
|
225
|
+
onItemHighlighted: (item, details) => {
|
|
226
|
+
setHighlightedValue(item?.value ?? null);
|
|
227
|
+
if (details.reason === "keyboard" && details.index >= 0) refScrollTo.current(details.index);
|
|
228
|
+
},
|
|
229
|
+
children: [/* @__PURE__ */ jsxs(Tooltip, {
|
|
230
|
+
disabled: popupOpen,
|
|
231
|
+
onOpenChange: (open, details) => {
|
|
232
|
+
const element = refTriggerText.current;
|
|
233
|
+
if (open && !(element && element.scrollWidth > element.clientWidth)) details.cancel();
|
|
234
|
+
},
|
|
235
|
+
children: [/* @__PURE__ */ jsxs(TooltipTrigger, {
|
|
236
|
+
"data-slot": "multi-select-trigger",
|
|
237
|
+
render: /* @__PURE__ */ jsx(Combobox.Trigger, {
|
|
238
|
+
...rest,
|
|
239
|
+
id,
|
|
240
|
+
"data-set": chosen.length > 0 ? "" : void 0,
|
|
241
|
+
onKeyDown: closeFromTrigger,
|
|
242
|
+
className: cn(TRIGGER, className)
|
|
243
|
+
}),
|
|
244
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
245
|
+
ref: refTriggerText,
|
|
246
|
+
className: cn("min-w-0 flex-1 truncate text-left", chosen.length === 0 && "text-muted-foreground"),
|
|
247
|
+
children: summarize(chosen, placeholder, labels, summary)
|
|
248
|
+
}), /* @__PURE__ */ jsx(IconChevronDown, {
|
|
249
|
+
"aria-hidden": "true",
|
|
250
|
+
className: "size-4 shrink-0 text-muted-foreground"
|
|
251
|
+
})]
|
|
252
|
+
}), /* @__PURE__ */ jsx(TooltipContent, { children: fullSelection(chosen, placeholder, labels, summary) })]
|
|
253
|
+
}), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
254
|
+
sideOffset: 4,
|
|
255
|
+
align: "start",
|
|
256
|
+
className: "z-50",
|
|
257
|
+
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
258
|
+
"data-slot": "multi-select-content",
|
|
259
|
+
className: POPUP,
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ jsxs("div", {
|
|
262
|
+
className: "flex shrink-0 items-center gap-2 border-b border-border px-3",
|
|
263
|
+
children: [/* @__PURE__ */ jsx(IconSearch, {
|
|
264
|
+
"aria-hidden": "true",
|
|
265
|
+
className: "size-4 shrink-0 text-muted-foreground"
|
|
266
|
+
}), /* @__PURE__ */ jsx(Combobox.Input, {
|
|
267
|
+
ref: refSearch,
|
|
268
|
+
placeholder: searchPlaceholder,
|
|
269
|
+
"aria-label": searchPlaceholder.replace(/…$/, ""),
|
|
270
|
+
className: "h-10 w-full min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
271
|
+
})]
|
|
272
|
+
}),
|
|
273
|
+
/* @__PURE__ */ jsx(Combobox.Status, {
|
|
274
|
+
"data-slot": "multi-select-status",
|
|
275
|
+
className: "px-3 py-2 text-sm text-muted-foreground empty:p-0",
|
|
276
|
+
children: failed ? /* @__PURE__ */ jsxs("span", {
|
|
277
|
+
className: "flex items-center justify-between gap-2 text-destructive",
|
|
278
|
+
children: [errorMessage, onRetry !== void 0 && /* @__PURE__ */ jsx("button", {
|
|
279
|
+
type: "button",
|
|
280
|
+
onClick: onRetry,
|
|
281
|
+
className: "rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6",
|
|
282
|
+
children: labels.retry
|
|
283
|
+
})]
|
|
284
|
+
}) : loading ? labels.loading : null
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ jsx(Combobox.Empty, {
|
|
287
|
+
className: "px-3 py-6 text-center text-sm text-muted-foreground empty:p-0",
|
|
288
|
+
children: loading || failed ? null : labels.noMatches
|
|
289
|
+
}),
|
|
290
|
+
/* @__PURE__ */ jsx(ScrollArea, {
|
|
291
|
+
type: "always",
|
|
292
|
+
fade: true,
|
|
293
|
+
viewportRef: refScroller,
|
|
294
|
+
className: "max-h-72 min-h-0",
|
|
295
|
+
children: /* @__PURE__ */ jsx(Options, {
|
|
296
|
+
scroller: refScroller,
|
|
297
|
+
scrollTo: refScrollTo,
|
|
298
|
+
busy: loading,
|
|
299
|
+
highlightedValue
|
|
300
|
+
})
|
|
301
|
+
}),
|
|
302
|
+
hasMore && onLoadMore !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
303
|
+
className: "shrink-0 border-t border-border p-1",
|
|
304
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
305
|
+
type: "button",
|
|
306
|
+
disabled: loadingMore,
|
|
307
|
+
onClick: onLoadMore,
|
|
308
|
+
className: "h-8 w-full rounded-md px-2 text-sm font-medium text-foreground hover:bg-foreground/6 disabled:pointer-events-none disabled:opacity-50",
|
|
309
|
+
children: loadingMore ? labels.loadingMore : labels.loadMore
|
|
310
|
+
})
|
|
311
|
+
}),
|
|
312
|
+
chosen.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
313
|
+
className: "flex shrink-0 items-center justify-between gap-2 border-t border-border py-1 pr-1 pl-3 text-xs text-muted-foreground",
|
|
314
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
315
|
+
className: "tabular-nums",
|
|
316
|
+
children: labels.selectedCount(chosen.length)
|
|
317
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
318
|
+
type: "button",
|
|
319
|
+
onClick: () => onValueChange([]),
|
|
320
|
+
className: "rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6",
|
|
321
|
+
children: labels.clear
|
|
322
|
+
})]
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
})
|
|
326
|
+
}) })]
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
330
|
+
export { DEFAULT_MULTI_SELECT_LABELS, MultiSelect, VIRTUALIZE_FROM, fullSelection, resolveChosen, summarize };
|
|
331
|
+
|
|
332
|
+
//# sourceMappingURL=multi-select.js.map
|