@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
package/dist/source-hash.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2504fece68d0bff41b5b90bc32becc10bb88aa3a7ffe2fd419071268e0915306
|
package/dist/styles.css
CHANGED
|
@@ -2975,14 +2975,6 @@
|
|
|
2975
2975
|
color: var(--accent-foreground);
|
|
2976
2976
|
}
|
|
2977
2977
|
|
|
2978
|
-
.group-has-disabled\/field\:pointer-events-none:is(:where(.group\/field):has(:disabled) *) {
|
|
2979
|
-
pointer-events: none;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
.group-has-disabled\/field\:opacity-50:is(:where(.group\/field):has(:disabled) *) {
|
|
2983
|
-
opacity: .5;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
2978
|
.group-has-data-\[sidebar\=menu-action\]\/menu-item\:pr-8:is(:where(.group\/menu-item):has([data-sidebar="menu-action"]) *) {
|
|
2987
2979
|
padding-right: calc(var(--spacing) * 8);
|
|
2988
2980
|
}
|
|
@@ -5810,16 +5802,6 @@
|
|
|
5810
5802
|
}
|
|
5811
5803
|
}
|
|
5812
5804
|
|
|
5813
|
-
.dark\:bg-destructive\/20:is(.dark *) {
|
|
5814
|
-
background-color: var(--destructive);
|
|
5815
|
-
}
|
|
5816
|
-
|
|
5817
|
-
@supports (color: color-mix(in lab, red, red)) {
|
|
5818
|
-
.dark\:bg-destructive\/20:is(.dark *) {
|
|
5819
|
-
background-color: color-mix(in oklab, var(--destructive) 20%, transparent);
|
|
5820
|
-
}
|
|
5821
|
-
}
|
|
5822
|
-
|
|
5823
5805
|
.dark\:bg-info-ring\/30:is(.dark *) {
|
|
5824
5806
|
background-color: var(--info-ring);
|
|
5825
5807
|
}
|
|
@@ -6326,6 +6308,10 @@
|
|
|
6326
6308
|
}
|
|
6327
6309
|
}
|
|
6328
6310
|
|
|
6311
|
+
.data-disabled\:text-muted-foreground:where([data-disabled="true"]), .data-disabled\:text-muted-foreground:where([data-disabled]:not([data-disabled="false"])) {
|
|
6312
|
+
color: var(--muted-foreground);
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6329
6315
|
.data-disabled\:opacity-50:where([data-disabled="true"]), .data-disabled\:opacity-50:where([data-disabled]:not([data-disabled="false"])) {
|
|
6330
6316
|
opacity: .5;
|
|
6331
6317
|
}
|
|
@@ -7475,7 +7461,7 @@
|
|
|
7475
7461
|
--muted-foreground: oklch(70.8% 0 0);
|
|
7476
7462
|
--accent: oklch(26.9% 0 0);
|
|
7477
7463
|
--accent-foreground: oklch(98.5% 0 0);
|
|
7478
|
-
--destructive: oklch(
|
|
7464
|
+
--destructive: oklch(76% .145 22.216);
|
|
7479
7465
|
--destructive-foreground: oklch(98.5% 0 0);
|
|
7480
7466
|
--success: oklch(79.2% .209 151.711);
|
|
7481
7467
|
--success-foreground: oklch(26.2% .051 172.552);
|
package/dist/theme.css
CHANGED
|
@@ -183,7 +183,8 @@
|
|
|
183
183
|
--accent: oklch(0.269 0 0);
|
|
184
184
|
--accent-foreground: oklch(0.985 0 0);
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
/* Light enough that text in it, and the destructive Badge's text on its own tint, is 4.5:1 or more on the page, a card and a muted surface. */
|
|
187
|
+
--destructive: oklch(0.76 0.145 22.216);
|
|
187
188
|
--destructive-foreground: oklch(0.985 0 0);
|
|
188
189
|
|
|
189
190
|
--success: oklch(0.792 0.209 151.711);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region ../ui/src/components/edyn/animated-number/animated-number.tsx
|
|
4
|
+
var DURATION = 800;
|
|
5
|
+
var easeInOutCubic = (t) => t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
|
|
6
|
+
var reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
7
|
+
var NUMBER = new Intl.NumberFormat("en-GB");
|
|
8
|
+
/** Thousands separated, rounded to a whole number: the default way a count reads. */
|
|
9
|
+
var formatCount = (value) => NUMBER.format(Math.round(value) || 0);
|
|
10
|
+
var useSeen = (element) => {
|
|
11
|
+
const [seen, setSeen] = useState(() => typeof IntersectionObserver === "undefined");
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const target = element.current;
|
|
14
|
+
if (seen || target === null) return;
|
|
15
|
+
const observer = new IntersectionObserver((entries) => {
|
|
16
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
17
|
+
setSeen(true);
|
|
18
|
+
observer.disconnect();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
observer.observe(target);
|
|
22
|
+
return () => observer.disconnect();
|
|
23
|
+
}, [element, seen]);
|
|
24
|
+
return seen;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The values to show for `targets` this frame, all on one clock and one curve, so a
|
|
28
|
+
* count and its bar move together. They start from 0 and move once `element` has
|
|
29
|
+
* been on screen.
|
|
30
|
+
*/
|
|
31
|
+
var useAnimatedValues = (targets, element) => {
|
|
32
|
+
const seen = useSeen(element);
|
|
33
|
+
const [shown, setShown] = useState(() => targets.map(() => 0));
|
|
34
|
+
const refCurrent = useRef(shown);
|
|
35
|
+
const key = targets.join(",");
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!seen) return;
|
|
38
|
+
const goal = key.split(",").map(Number);
|
|
39
|
+
const from = goal.map((_, index) => refCurrent.current[index] ?? 0);
|
|
40
|
+
if (from.every((value, index) => value === goal[index])) return;
|
|
41
|
+
if (reducedMotion()) {
|
|
42
|
+
refCurrent.current = goal;
|
|
43
|
+
setShown(goal);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const start = performance.now();
|
|
47
|
+
let frame = 0;
|
|
48
|
+
const tick = (now) => {
|
|
49
|
+
const progress = Math.min(1, (now - start) / DURATION);
|
|
50
|
+
const eased = easeInOutCubic(progress);
|
|
51
|
+
const next = progress === 1 ? goal : goal.map((value, index) => (from[index] ?? 0) + (value - (from[index] ?? 0)) * eased);
|
|
52
|
+
refCurrent.current = next;
|
|
53
|
+
setShown(next);
|
|
54
|
+
if (progress < 1) frame = requestAnimationFrame(tick);
|
|
55
|
+
};
|
|
56
|
+
frame = requestAnimationFrame(tick);
|
|
57
|
+
return () => cancelAnimationFrame(frame);
|
|
58
|
+
}, [key, seen]);
|
|
59
|
+
return shown;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A number that moves to its new value. Its width holds the widest of where it starts
|
|
63
|
+
* and where it lands, in tabular figures, so nothing beside it moves while the digits
|
|
64
|
+
* change. Assistive technology reads only the final value.
|
|
65
|
+
*/
|
|
66
|
+
var AnimatedNumber = ({ value, format = formatCount, className, ...props }) => {
|
|
67
|
+
const refElement = useRef(null);
|
|
68
|
+
const [shown = 0] = useAnimatedValues([value], refElement);
|
|
69
|
+
return /* @__PURE__ */ jsx(NumberText, {
|
|
70
|
+
element: refElement,
|
|
71
|
+
shown,
|
|
72
|
+
value,
|
|
73
|
+
format,
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
/** The text of a moving number: what it shows now, held at its widest, with the final value for assistive technology. */
|
|
79
|
+
var NumberText = ({ element, shown, value, format = formatCount, className, ...props }) => {
|
|
80
|
+
const now = format(shown);
|
|
81
|
+
const final = format(value);
|
|
82
|
+
const widest = now.length > final.length ? now : final;
|
|
83
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
84
|
+
"data-slot": "animated-number",
|
|
85
|
+
...props,
|
|
86
|
+
ref: element,
|
|
87
|
+
className,
|
|
88
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
className: "inline-grid tabular-nums",
|
|
91
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
92
|
+
"data-ghost": widest,
|
|
93
|
+
className: "invisible col-start-1 row-start-1 ghost-text"
|
|
94
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
95
|
+
"data-slot": "animated-number-value",
|
|
96
|
+
className: "col-start-1 row-start-1",
|
|
97
|
+
children: now
|
|
98
|
+
})]
|
|
99
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
100
|
+
className: "sr-only",
|
|
101
|
+
children: final
|
|
102
|
+
})]
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { AnimatedNumber, NumberText, formatCount, useAnimatedValues };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=animated-number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animated-number.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/animated-number/animated-number.tsx"],"sourcesContent":["import { type ComponentPropsWithoutRef, type RefObject, useEffect, useRef, useState } from 'react';\n\n// Numbers that move instead of jumping when a tab or a filter changes them: from the\n// value shown to the new one over 800ms, easing in and out; the first time, from 0\n// once it scrolls into view. Interrupted by another change, it carries on from where\n// it is, never back. With reduced motion, or when the value has not changed (a refetch\n// with the same answer), nothing moves.\n\nconst DURATION = 800;\n\nconst easeInOutCubic = (t: number): number => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2);\n\nconst reducedMotion = (): boolean => window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\nconst NUMBER = new Intl.NumberFormat('en-GB');\n\n/** Thousands separated, rounded to a whole number: the default way a count reads. */\nexport const formatCount = (value: number): string => NUMBER.format(Math.round(value) || 0);\n\n// Whether the element has been on screen yet; once it has, it stays counted as seen.\nconst useSeen = (element: RefObject<Element | null>): boolean => {\n const [seen, setSeen] = useState(() => typeof IntersectionObserver === 'undefined');\n useEffect(() => {\n const target = element.current;\n if (seen || target === null) {\n return;\n }\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setSeen(true);\n observer.disconnect();\n }\n });\n observer.observe(target);\n return () => observer.disconnect();\n }, [element, seen]);\n return seen;\n};\n\n/**\n * The values to show for `targets` this frame, all on one clock and one curve, so a\n * count and its bar move together. They start from 0 and move once `element` has\n * been on screen.\n */\nexport const useAnimatedValues = (targets: number[], element: RefObject<Element | null>): number[] => {\n const seen = useSeen(element);\n const [shown, setShown] = useState<number[]>(() => targets.map(() => 0));\n const refCurrent = useRef(shown);\n const key = targets.join(',');\n useEffect(() => {\n if (!seen) {\n return;\n }\n const goal = key.split(',').map(Number);\n const from = goal.map((_, index) => refCurrent.current[index] ?? 0);\n if (from.every((value, index) => value === goal[index])) {\n return;\n }\n if (reducedMotion()) {\n refCurrent.current = goal;\n setShown(goal);\n return;\n }\n const start = performance.now();\n let frame = 0;\n const tick = (now: number): void => {\n const progress = Math.min(1, (now - start) / DURATION);\n const eased = easeInOutCubic(progress);\n // Exactly the goal at the end, never a float a hair beside it.\n const next = progress === 1 ? goal : goal.map((value, index) => (from[index] ?? 0) + (value - (from[index] ?? 0)) * eased);\n refCurrent.current = next;\n setShown(next);\n if (progress < 1) {\n frame = requestAnimationFrame(tick);\n }\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }, [key, seen]);\n return shown;\n};\n\nexport interface IAnimatedNumberProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n value: number;\n /** What it shows while it moves and once there (formatCount by default; a percentage formatter for shares). */\n format?: (value: number) => string;\n className?: string;\n}\n\n/**\n * A number that moves to its new value. Its width holds the widest of where it starts\n * and where it lands, in tabular figures, so nothing beside it moves while the digits\n * change. Assistive technology reads only the final value.\n */\nexport const AnimatedNumber = ({ value, format = formatCount, className, ...props }: IAnimatedNumberProps) => {\n const refElement = useRef<HTMLSpanElement>(null);\n const [shown = 0] = useAnimatedValues([value], refElement);\n return <NumberText element={refElement} shown={shown} value={value} format={format} className={className} {...props} />;\n};\n\nexport interface INumberTextProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n element?: RefObject<HTMLSpanElement | null>;\n shown: number;\n value: number;\n format?: (value: number) => string;\n className?: string;\n}\n\n/** The text of a moving number: what it shows now, held at its widest, with the final value for assistive technology. */\nexport const NumberText = ({ element, shown, value, format = formatCount, className, ...props }: INumberTextProps) => {\n const now = format(shown);\n const final = format(value);\n // The widest of the two, laid invisibly under the number: it holds the width of the\n // real figures (tabular digits are not exactly 1ch), so the number grows or shrinks\n // only once it has landed.\n const widest = now.length > final.length ? now : final;\n return (\n <span data-slot=\"animated-number\" {...props} ref={element} className={className}>\n <span aria-hidden=\"true\" className=\"inline-grid tabular-nums\">\n <span data-ghost={widest} className=\"invisible col-start-1 row-start-1 ghost-text\" />\n <span data-slot=\"animated-number-value\" className=\"col-start-1 row-start-1\">\n {now}\n </span>\n </span>\n <span className=\"sr-only\">{final}</span>\n </span>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { cn, cssLength } from "../../../lib/utils.js";
|
|
2
|
+
import { Button } from "../../ui/button/button.js";
|
|
3
|
+
import { Slider } from "../../ui/slider/slider.js";
|
|
4
|
+
import { formatMediaTime, useMediaPlayer } from "../../../hooks/use-media-player.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { IconPlayerPauseFilled, IconPlayerPlayFilled, IconVolume, IconVolumeOff } from "@tabler/icons-react";
|
|
7
|
+
//#region ../ui/src/components/edyn/audio-player/audio-player.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Compact inline media control for audio content: play/pause, elapsed and total
|
|
10
|
+
* time, a seekable timeline, and volume. Four elements in one rounded row, per
|
|
11
|
+
* the design-system anatomy.
|
|
12
|
+
*/
|
|
13
|
+
var AudioPlayer = ({ src, muted = false, loop = false, disabled = false, width, className, "aria-label": ariaLabel = "Audio player", style, onPlay, onPause, onEnded, onTimeUpdate, ...props }) => {
|
|
14
|
+
const { refMedia, playing, currentTime, duration, muted: isMuted, toggle, seek, toggleMute } = useMediaPlayer({
|
|
15
|
+
onPlay,
|
|
16
|
+
onPause,
|
|
17
|
+
onEnded,
|
|
18
|
+
onTimeUpdate
|
|
19
|
+
});
|
|
20
|
+
const PlaybackIcon = playing ? IconPlayerPauseFilled : IconPlayerPlayFilled;
|
|
21
|
+
const VolumeIcon = isMuted ? IconVolumeOff : IconVolume;
|
|
22
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
23
|
+
"data-slot": "audio-player",
|
|
24
|
+
"data-playing": playing || void 0,
|
|
25
|
+
role: "group",
|
|
26
|
+
...props,
|
|
27
|
+
"aria-label": ariaLabel,
|
|
28
|
+
style: {
|
|
29
|
+
width: cssLength(width),
|
|
30
|
+
...style
|
|
31
|
+
},
|
|
32
|
+
className: cn("flex h-12 w-full items-center gap-2 rounded-lg bg-muted px-3 py-1.5", className),
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Button, {
|
|
35
|
+
type: "button",
|
|
36
|
+
variant: "ghost",
|
|
37
|
+
size: "icon",
|
|
38
|
+
disabled,
|
|
39
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
40
|
+
onClick: toggle,
|
|
41
|
+
children: /* @__PURE__ */ jsx(PlaybackIcon, {})
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsxs("span", {
|
|
44
|
+
"data-slot": "audio-player-time",
|
|
45
|
+
className: "shrink-0 text-xs tabular-nums text-foreground/70",
|
|
46
|
+
children: [
|
|
47
|
+
formatMediaTime(currentTime),
|
|
48
|
+
" / ",
|
|
49
|
+
formatMediaTime(duration)
|
|
50
|
+
]
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ jsx(Slider, {
|
|
53
|
+
className: "flex-1 [&_[data-slot=slider-control]]:py-1.5 [&_[data-slot=slider-thumb]]:size-3 [&_[data-slot=slider-track]]:h-1 [&_[data-slot=slider-track]]:bg-foreground/15",
|
|
54
|
+
value: Math.min(currentTime, duration || 0),
|
|
55
|
+
min: 0,
|
|
56
|
+
max: duration || 0,
|
|
57
|
+
step: 1,
|
|
58
|
+
disabled: disabled || duration === 0,
|
|
59
|
+
"aria-label": "Seek",
|
|
60
|
+
onValueChange: (next) => {
|
|
61
|
+
seek(next);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ jsx(Button, {
|
|
65
|
+
type: "button",
|
|
66
|
+
variant: "ghost",
|
|
67
|
+
size: "icon",
|
|
68
|
+
disabled,
|
|
69
|
+
"aria-label": isMuted ? "Unmute" : "Mute",
|
|
70
|
+
onClick: toggleMute,
|
|
71
|
+
children: /* @__PURE__ */ jsx(VolumeIcon, {})
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ jsx("audio", {
|
|
74
|
+
ref: refMedia,
|
|
75
|
+
src,
|
|
76
|
+
muted,
|
|
77
|
+
loop,
|
|
78
|
+
preload: "metadata"
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { AudioPlayer };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=audio-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-player.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/audio-player/audio-player.tsx"],"sourcesContent":["import { Button } from '@edyn/ui/components/ui/button';\nimport { Slider } from '@edyn/ui/components/ui/slider';\nimport { formatMediaTime, useMediaPlayer } from '@edyn/ui/hooks/use-media-player';\nimport type { TCssLength } from '@edyn/ui/lib/utils';\nimport { cn, cssLength } from '@edyn/ui/lib/utils';\nimport { IconPlayerPauseFilled, IconPlayerPlayFilled, IconVolume, IconVolumeOff } from '@tabler/icons-react';\nimport type { ComponentPropsWithoutRef } from 'react';\n\nexport interface IAudioPlayerProps\n extends Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'className' | 'onPlay' | 'onPause' | 'onEnded' | 'onTimeUpdate' | 'role'> {\n /** Audio source URL. */\n src?: string;\n /** Start muted. The volume control still works from here. */\n muted?: boolean;\n /** Loop playback when the track ends. */\n loop?: boolean;\n /** Disable every control without hiding the player. */\n disabled?: boolean;\n /**\n * Bar width. A number is px, a string is any CSS length (`'100%'`, `'34rem'`).\n * Omitted, the bar fills the width it is given. The height is fixed at 48px by\n * the design: the row has to line up with the other form-height controls.\n */\n width?: TCssLength;\n className?: string;\n 'aria-label'?: string;\n onPlay?: () => void;\n onPause?: () => void;\n onEnded?: () => void;\n onTimeUpdate?: (currentTime: number, duration: number) => void;\n}\n\n/**\n * Compact inline media control for audio content: play/pause, elapsed and total\n * time, a seekable timeline, and volume. Four elements in one rounded row, per\n * the design-system anatomy.\n */\nexport const AudioPlayer = ({\n src,\n muted = false,\n loop = false,\n disabled = false,\n width,\n className,\n 'aria-label': ariaLabel = 'Audio player',\n style,\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n ...props\n}: IAudioPlayerProps) => {\n const {\n refMedia,\n playing,\n currentTime,\n duration,\n muted: isMuted,\n toggle,\n seek,\n toggleMute,\n } = useMediaPlayer<HTMLAudioElement>({\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n });\n\n const PlaybackIcon = playing ? IconPlayerPauseFilled : IconPlayerPlayFilled;\n const VolumeIcon = isMuted ? IconVolumeOff : IconVolume;\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> carries form semantics and UA styling this player does not want; role=\"group\" is the correct ARIA for a labelled set of related controls.\n <div\n data-slot=\"audio-player\"\n data-playing={playing || undefined}\n role=\"group\"\n {...props}\n aria-label={ariaLabel}\n style={{ width: cssLength(width), ...style }}\n className={cn('flex h-12 w-full items-center gap-2 rounded-lg bg-muted px-3 py-1.5', className)}\n >\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" disabled={disabled} aria-label={playing ? 'Pause' : 'Play'} onClick={toggle}>\n <PlaybackIcon />\n </Button>\n\n {/* `text-foreground/70` not `text-muted-foreground`: the latter on `bg-muted` is only 4.34:1, below AA. */}\n <span data-slot=\"audio-player-time\" className=\"shrink-0 text-xs tabular-nums text-foreground/70\">\n {formatMediaTime(currentTime)} / {formatMediaTime(duration)}\n </span>\n\n {/* Scope the form-sized Slider down to a timeline: thinner track, smaller thumb, and a track tint that\n reads against this bar (the Slider's default `bg-muted` track is invisible on a `bg-muted` container). */}\n <Slider\n className=\"flex-1 [&_[data-slot=slider-control]]:py-1.5 [&_[data-slot=slider-thumb]]:size-3 [&_[data-slot=slider-track]]:h-1 [&_[data-slot=slider-track]]:bg-foreground/15\"\n value={Math.min(currentTime, duration || 0)}\n min={0}\n max={duration || 0}\n step={1}\n disabled={disabled || duration === 0}\n aria-label=\"Seek\"\n onValueChange={(next) => {\n seek(next);\n }}\n />\n\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" disabled={disabled} aria-label={isMuted ? 'Unmute' : 'Mute'} onClick={toggleMute}>\n <VolumeIcon />\n </Button>\n\n {/* Visually hidden: the row above is the entire control surface. */}\n {/* No class needed: the UA stylesheet already hides `audio:not([controls])`, and that rule is `!important` so author CSS cannot change it either way. */}\n <audio ref={refMedia} src={src} muted={muted} loop={loop} preload=\"metadata\" />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAqCA,IAAa,eAAe,EAC1B,KACA,QAAQ,OACR,OAAO,OACP,WAAW,OACX,OACA,WACA,cAAc,YAAY,gBAC1B,OACA,QACA,SACA,SACA,cACA,GAAG,YACoB;CACvB,MAAM,EACJ,UACA,SACA,aACA,UACA,OAAO,SACP,QACA,MACA,eACE,eAAiC;EACnC;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,eAAe,UAAU,wBAAwB;CACvD,MAAM,aAAa,UAAU,gBAAgB;CAE7C,OAEE,qBAAC,OAAD;EACE,aAAU;EACV,gBAAc,WAAW,KAAA;EACzB,MAAK;EACL,GAAI;EACJ,cAAY;EACZ,OAAO;GAAE,OAAO,UAAU,KAAK;GAAG,GAAG;EAAM;EAC3C,WAAW,GAAG,uEAAuE,SAAS;EAPhG,UAAA;GASE,oBAAC,QAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;IAAiB;IAAU,cAAY,UAAU,UAAU;IAAQ,SAAS;IACrH,UAAA,oBAAC,cAAD,CAAe,CAAA;GACT,CAAA;GAGR,qBAAC,QAAD;IAAM,aAAU;IAAoB,WAAU;IAA9C,UAAA;KACG,gBAAgB,WAAW;KAAE;KAAI,gBAAgB,QAAQ;IACtD;;GAIN,oBAAC,QAAD;IACE,WAAU;IACV,OAAO,KAAK,IAAI,aAAa,YAAY,CAAC;IAC1C,KAAK;IACL,KAAK,YAAY;IACjB,MAAM;IACN,UAAU,YAAY,aAAa;IACnC,cAAW;IACX,gBAAgB,SAAS;KACvB,KAAK,IAAI;IACX;GACD,CAAA;GAED,oBAAC,QAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;IAAiB;IAAU,cAAY,UAAU,WAAW;IAAQ,SAAS;IACtH,UAAA,oBAAC,YAAD,CAAa,CAAA;GACP,CAAA;GAIR,oBAAC,SAAD;IAAO,KAAK;IAAe;IAAY;IAAa;IAAM,SAAQ;GAAY,CAAA;EAC3E;;AAET"}
|
|
@@ -0,0 +1,250 @@
|
|
|
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 { firstWeekdayOf } from "../../../lib/first-weekday.js";
|
|
6
|
+
import { dateToDay, dayToDate } from "../date-range-picker/date-range-picker.js";
|
|
7
|
+
import { Input } from "../../ui/input/input.js";
|
|
8
|
+
import { datePatternOf, dayProblem, formatDay, parseDateText } from "./date-text.js";
|
|
9
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { IconCalendar } from "@tabler/icons-react";
|
|
12
|
+
//#region ../ui/src/components/edyn/date-picker/date-picker.tsx
|
|
13
|
+
var DEFAULT_DATE_PICKER_LABELS = {
|
|
14
|
+
choose: "Choose date",
|
|
15
|
+
format: (pattern) => `Enter a date as ${pattern}.`,
|
|
16
|
+
notReal: "That date does not exist.",
|
|
17
|
+
beforeMin: (min) => `Choose ${min} or later.`,
|
|
18
|
+
afterMax: (max) => `Choose ${max} or earlier.`,
|
|
19
|
+
unavailable: "That date is not available."
|
|
20
|
+
};
|
|
21
|
+
var problemText = (problem, labels, locale, limits, pattern) => {
|
|
22
|
+
switch (problem) {
|
|
23
|
+
case "format": return labels.format(pattern);
|
|
24
|
+
case "notReal": return labels.notReal;
|
|
25
|
+
case "beforeMin": return labels.beforeMin(limits.min === void 0 ? "" : formatDay(limits.min, locale));
|
|
26
|
+
case "afterMax": return labels.afterMax(limits.max === void 0 ? "" : formatDay(limits.max, locale));
|
|
27
|
+
default: return labels.unavailable;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* One calendar day, typed or picked. The field takes the day in the locale's order (05/03/2026 in en-GB) and reads it
|
|
32
|
+
* when it loses focus, saying inline what is wrong with it; the button, or Alt+ArrowDown in the field, opens a calendar.
|
|
33
|
+
*/
|
|
34
|
+
var DatePicker = ({ id, value, onValueChange, min, max, disabledDates, disabled = false, locale = "en-GB", calendarLocale, placeholder, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, labels: labelOverrides, className }) => {
|
|
35
|
+
const labels = useMemo(() => ({
|
|
36
|
+
...DEFAULT_DATE_PICKER_LABELS,
|
|
37
|
+
...labelOverrides
|
|
38
|
+
}), [labelOverrides]);
|
|
39
|
+
const pattern = useMemo(() => datePatternOf(locale), [locale]);
|
|
40
|
+
const current = value === "" ? void 0 : value;
|
|
41
|
+
const errorId = useId();
|
|
42
|
+
const refField = useRef(null);
|
|
43
|
+
const refInput = useRef(null);
|
|
44
|
+
const refPointer = useRef(false);
|
|
45
|
+
const refKnown = useRef(current);
|
|
46
|
+
const refRefused = useRef(null);
|
|
47
|
+
const [text, setText] = useState(current === void 0 ? "" : formatDay(current, locale));
|
|
48
|
+
const [problem, setProblem] = useState(null);
|
|
49
|
+
const [open, setOpen] = useState(false);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (current !== refKnown.current) {
|
|
52
|
+
refKnown.current = current;
|
|
53
|
+
refRefused.current = null;
|
|
54
|
+
setText(current === void 0 ? "" : formatDay(current, locale));
|
|
55
|
+
setProblem(null);
|
|
56
|
+
}
|
|
57
|
+
}, [current, locale]);
|
|
58
|
+
const report = (day, state) => {
|
|
59
|
+
refKnown.current = day;
|
|
60
|
+
refRefused.current = state.kind === "refused" ? `${state.text}\u0000${state.problem}` : null;
|
|
61
|
+
onValueChange(day, state);
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Reads the text: a day is reported and written back in full, anything else is said inline and reported as no day,
|
|
65
|
+
* with the state saying whether the field is empty or holds text it refused.
|
|
66
|
+
*/
|
|
67
|
+
const commit = (typed) => {
|
|
68
|
+
const parsed = parseDateText(typed, locale);
|
|
69
|
+
if (parsed.kind === "empty") {
|
|
70
|
+
setProblem(null);
|
|
71
|
+
if (current !== void 0 || refRefused.current !== null) report(void 0, { kind: "empty" });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const found = parsed.kind === "problem" ? parsed.problem : dayProblem(parsed.day, {
|
|
75
|
+
min,
|
|
76
|
+
max,
|
|
77
|
+
disabledDates
|
|
78
|
+
});
|
|
79
|
+
if (found !== void 0) {
|
|
80
|
+
setProblem(found);
|
|
81
|
+
const message = problemText(found, labels, locale, {
|
|
82
|
+
min,
|
|
83
|
+
max
|
|
84
|
+
}, pattern);
|
|
85
|
+
if (current !== void 0 || refRefused.current !== `${typed}\u0000${message}`) report(void 0, {
|
|
86
|
+
kind: "refused",
|
|
87
|
+
text: typed,
|
|
88
|
+
problem: message
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (parsed.kind === "day") {
|
|
93
|
+
setProblem(null);
|
|
94
|
+
setText(formatDay(parsed.day, locale));
|
|
95
|
+
if (parsed.day !== current || refRefused.current !== null) report(parsed.day, { kind: "day" });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const pick = (date) => {
|
|
99
|
+
const day = dateToDay(date);
|
|
100
|
+
if (day === void 0) return;
|
|
101
|
+
setProblem(null);
|
|
102
|
+
setText(formatDay(day, locale));
|
|
103
|
+
if (day !== current || refRefused.current !== null) report(day, { kind: "day" });
|
|
104
|
+
setOpen(false);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Text is read as the person leaves the field. A day (or nothing) is taken at once; a message about text that is not a
|
|
108
|
+
* day is shown too, unless they are only on the way to the calendar: a press of the mouse on the button, or focus
|
|
109
|
+
* going into the calendar that Alt+ArrowDown opened, with the text half typed. Leaving the picker for anywhere else
|
|
110
|
+
* reads whatever is typed.
|
|
111
|
+
*/
|
|
112
|
+
const handleBlur = (event) => {
|
|
113
|
+
const next = event.relatedTarget;
|
|
114
|
+
const intoCalendar = next?.closest("[data-slot=\"popover-content\"]") != null;
|
|
115
|
+
const intoButton = next?.closest("[data-slot=\"date-picker-trigger\"]") != null;
|
|
116
|
+
const typed = refInput.current?.value ?? "";
|
|
117
|
+
if (!(intoCalendar || intoButton || next !== null && refField.current?.contains(next) === true)) {
|
|
118
|
+
commit(typed);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (event.target !== refInput.current) return;
|
|
122
|
+
const onTheWay = intoCalendar || intoButton && refPointer.current;
|
|
123
|
+
const parsed = parseDateText(typed, locale);
|
|
124
|
+
if (parsed.kind === "empty" || parsed.kind === "day" && dayProblem(parsed.day, {
|
|
125
|
+
min,
|
|
126
|
+
max,
|
|
127
|
+
disabledDates
|
|
128
|
+
}) === void 0 || !onTheWay) commit(typed);
|
|
129
|
+
};
|
|
130
|
+
const handleKeyDown = (event) => {
|
|
131
|
+
if (event.key === "ArrowDown" && event.altKey && !disabled) {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
setOpen(true);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (event.key === "Escape" && open) {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
event.stopPropagation();
|
|
139
|
+
setOpen(false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (event.key === "Enter") {
|
|
143
|
+
const parsed = parseDateText(event.currentTarget.value, locale);
|
|
144
|
+
if (parsed.kind !== "empty" && (parsed.kind === "problem" || dayProblem(parsed.day, {
|
|
145
|
+
min,
|
|
146
|
+
max,
|
|
147
|
+
disabledDates
|
|
148
|
+
}) !== void 0)) event.preventDefault();
|
|
149
|
+
commit(event.currentTarget.value);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const selected = dayToDate(current);
|
|
153
|
+
const limits = {
|
|
154
|
+
min,
|
|
155
|
+
max
|
|
156
|
+
};
|
|
157
|
+
const invalid = problem !== null || ariaInvalid === true || ariaInvalid === "true";
|
|
158
|
+
const describedBy = [ariaDescribedBy, problem === null ? void 0 : errorId].filter(Boolean).join(" ") || void 0;
|
|
159
|
+
const isOff = (date) => {
|
|
160
|
+
const day = dateToDay(date);
|
|
161
|
+
return day !== void 0 && dayProblem(day, {
|
|
162
|
+
min,
|
|
163
|
+
max,
|
|
164
|
+
disabledDates
|
|
165
|
+
}) !== void 0;
|
|
166
|
+
};
|
|
167
|
+
const today = /* @__PURE__ */ new Date();
|
|
168
|
+
const start = dayToDate(min);
|
|
169
|
+
const end = dayToDate(max);
|
|
170
|
+
const month = selected ?? (start !== void 0 && today < start ? start : end !== void 0 && today > end ? end : today);
|
|
171
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
172
|
+
"data-slot": "date-picker",
|
|
173
|
+
className: cn("flex flex-col gap-1.5", className),
|
|
174
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
175
|
+
ref: refField,
|
|
176
|
+
className: "relative",
|
|
177
|
+
onPointerDownCapture: () => {
|
|
178
|
+
refPointer.current = true;
|
|
179
|
+
},
|
|
180
|
+
onPointerUpCapture: () => {
|
|
181
|
+
refPointer.current = false;
|
|
182
|
+
},
|
|
183
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
184
|
+
ref: refInput,
|
|
185
|
+
id,
|
|
186
|
+
type: "text",
|
|
187
|
+
inputMode: "numeric",
|
|
188
|
+
autoComplete: "off",
|
|
189
|
+
"data-slot": "date-picker-input",
|
|
190
|
+
value: text,
|
|
191
|
+
placeholder: placeholder ?? pattern,
|
|
192
|
+
disabled,
|
|
193
|
+
"aria-label": ariaLabel,
|
|
194
|
+
"aria-invalid": invalid ? true : void 0,
|
|
195
|
+
"aria-describedby": describedBy,
|
|
196
|
+
className: "pr-9",
|
|
197
|
+
onChange: (event) => {
|
|
198
|
+
setText(event.target.value);
|
|
199
|
+
setProblem(null);
|
|
200
|
+
},
|
|
201
|
+
onBlur: handleBlur,
|
|
202
|
+
onKeyDown: handleKeyDown
|
|
203
|
+
}), /* @__PURE__ */ jsxs(Popover, {
|
|
204
|
+
open,
|
|
205
|
+
onOpenChange: setOpen,
|
|
206
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
207
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
208
|
+
variant: "ghost",
|
|
209
|
+
size: "icon-sm",
|
|
210
|
+
"data-slot": "date-picker-trigger",
|
|
211
|
+
"aria-label": labels.choose,
|
|
212
|
+
disabled,
|
|
213
|
+
className: "absolute top-0.5 right-0.5",
|
|
214
|
+
onBlur: handleBlur
|
|
215
|
+
}),
|
|
216
|
+
children: /* @__PURE__ */ jsx(IconCalendar, { "aria-hidden": "true" })
|
|
217
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
218
|
+
anchor: refField,
|
|
219
|
+
align: "start",
|
|
220
|
+
className: "w-auto p-0",
|
|
221
|
+
finalFocus: refInput,
|
|
222
|
+
onBlur: handleBlur,
|
|
223
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
224
|
+
mode: "single",
|
|
225
|
+
required: true,
|
|
226
|
+
autoFocus: true,
|
|
227
|
+
locale: calendarLocale,
|
|
228
|
+
weekStartsOn: firstWeekdayOf(locale),
|
|
229
|
+
defaultMonth: month,
|
|
230
|
+
startMonth: start,
|
|
231
|
+
endMonth: end,
|
|
232
|
+
selected,
|
|
233
|
+
disabled: isOff,
|
|
234
|
+
onSelect: pick
|
|
235
|
+
})
|
|
236
|
+
})]
|
|
237
|
+
})]
|
|
238
|
+
}), problem !== null && /* @__PURE__ */ jsx("p", {
|
|
239
|
+
id: errorId,
|
|
240
|
+
"data-slot": "date-picker-error",
|
|
241
|
+
role: "alert",
|
|
242
|
+
className: "text-sm text-destructive",
|
|
243
|
+
children: problemText(problem, labels, locale, limits, pattern)
|
|
244
|
+
})]
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
//#endregion
|
|
248
|
+
export { DEFAULT_DATE_PICKER_LABELS, DatePicker };
|
|
249
|
+
|
|
250
|
+
//# sourceMappingURL=date-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-picker.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/date-picker/date-picker.tsx"],"sourcesContent":["import { dateToDay, dayToDate } from '@edyn/ui/components/edyn/date-range-picker';\nimport { Button } from '@edyn/ui/components/ui/button';\nimport { Calendar } from '@edyn/ui/components/ui/calendar';\nimport { Input } from '@edyn/ui/components/ui/input';\nimport { Popover, PopoverContent, PopoverTrigger } from '@edyn/ui/components/ui/popover';\nimport { firstWeekdayOf } from '@edyn/ui/lib/first-weekday';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCalendar } from '@tabler/icons-react';\nimport { type ChangeEvent, type FocusEvent, type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport type { Locale } from 'react-day-picker';\nimport { datePatternOf, dayProblem, formatDay, parseDateText, type TDateProblem, type TDisabledDays } from './date-text';\n\n/** Every word the component prints, so a product can translate it. */\nexport interface IDatePickerLabels {\n /** The calendar button's accessible name. */\n choose: string;\n /** Said when the text is not a date; `pattern` is what to type, written the locale's way (`DD/MM/YYYY`). */\n format: (pattern: string) => string;\n notReal: string;\n /** `min` and `max` are written the locale's way. */\n beforeMin: (min: string) => string;\n afterMax: (max: string) => string;\n unavailable: string;\n}\n\nexport const DEFAULT_DATE_PICKER_LABELS: IDatePickerLabels = {\n choose: 'Choose date',\n format: (pattern) => `Enter a date as ${pattern}.`,\n notReal: 'That date does not exist.',\n beforeMin: (min) => `Choose ${min} or later.`,\n afterMax: (max) => `Choose ${max} or earlier.`,\n unavailable: 'That date is not available.',\n};\n\n/**\n * What the field holds when it reports, so a parent can tell three things apart that all arrive as `undefined`:\n * - `day`: a usable day, typed or picked.\n * - `empty`: the field was emptied, or nothing has been put in it. A required field is right to ask for a date.\n * - `refused`: the field holds text it will not take (not a day, not a real one, or outside `min`, `max` or\n * `disabledDates`). `text` is what was typed and `problem` the message the picker shows under the field, already\n * in the picker's `labels`. The picker is saying so itself, so the page should not add a second message.\n */\nexport type TDatePickerState = { kind: 'day' } | { kind: 'empty' } | { kind: 'refused'; text: string; problem: string };\n\nexport interface IDatePickerProps {\n /** For a Label's htmlFor; inside a Field the Field's label is already wired. */\n id?: string;\n /**\n * The chosen day as `yyyy-mm-dd`, or `undefined` for none (an empty string counts as none, so a value kept for an\n * `<input type=\"date\">` drops in). A string and not a Date, on purpose: a Date is an instant, and the instant for\n * \"5 March\" is another calendar day in another time zone, so a Date serialised for an API drifts a day. The text is\n * what `<input type=\"date\">`, JSON and a .NET `DateOnly` use, and it compares as a day.\n */\n value: string | undefined;\n /**\n * Called with a valid day, or with `undefined` when the field is emptied or its text is not a usable day. The second\n * argument says which of the two it was (see `TDatePickerState`); a caller that has no use for it can ignore it.\n * Text that is refused is reported each time it changes, also when the field held no day before, so a form that\n * tracks the state hears about it. Emptying the field after that reports `empty`.\n */\n onValueChange: (value: string | undefined, state: TDatePickerState) => void;\n /** The earliest and latest day that can be chosen, as `yyyy-mm-dd`, both included. */\n min?: string;\n max?: string;\n /** Days that cannot be chosen: a list of `yyyy-mm-dd`, or a function that says so for one (weekends, say). */\n disabledDates?: TDisabledDays;\n disabled?: boolean;\n /** How a day is written and in which order it is typed (BCP 47); en-GB reads 05/03/2026. */\n locale?: string;\n /** The calendar's own words (month and weekday names), as a date-fns locale; English by default. The first weekday follows `locale`. */\n calendarLocale?: Locale;\n /** What the empty field shows; the pattern for the locale (`DD/MM/YYYY`) by default. */\n placeholder?: string;\n /** An accessible name, when there is no visible label to point at. */\n 'aria-label'?: string;\n /** Marks the field invalid (the kit's destructive border), as a Field's invalid state does. */\n 'aria-invalid'?: boolean | 'true' | 'false';\n /** The id of the text that explains it. Added to what a Field names already, and to the picker's own error. */\n 'aria-describedby'?: string;\n labels?: Partial<IDatePickerLabels>;\n /** Layout only (a width); the look is the kit's. */\n className?: string;\n}\n\nconst problemText = (\n problem: TDateProblem,\n labels: IDatePickerLabels,\n locale: string,\n limits: { min?: string; max?: string },\n pattern: string,\n): string => {\n switch (problem) {\n case 'format':\n return labels.format(pattern);\n case 'notReal':\n return labels.notReal;\n case 'beforeMin':\n return labels.beforeMin(limits.min === undefined ? '' : formatDay(limits.min, locale));\n case 'afterMax':\n return labels.afterMax(limits.max === undefined ? '' : formatDay(limits.max, locale));\n default:\n return labels.unavailable;\n }\n};\n\n/**\n * One calendar day, typed or picked. The field takes the day in the locale's order (05/03/2026 in en-GB) and reads it\n * when it loses focus, saying inline what is wrong with it; the button, or Alt+ArrowDown in the field, opens a calendar.\n */\nexport const DatePicker = ({\n id,\n value,\n onValueChange,\n min,\n max,\n disabledDates,\n disabled = false,\n locale = 'en-GB',\n calendarLocale,\n placeholder,\n 'aria-label': ariaLabel,\n 'aria-invalid': ariaInvalid,\n 'aria-describedby': ariaDescribedBy,\n labels: labelOverrides,\n className,\n}: IDatePickerProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_DATE_PICKER_LABELS, ...labelOverrides }), [labelOverrides]);\n const pattern = useMemo(() => datePatternOf(locale), [locale]);\n const current = value === '' ? undefined : value;\n const errorId = useId();\n const refField = useRef<HTMLDivElement | null>(null);\n const refInput = useRef<HTMLInputElement | null>(null);\n // The mouse is down inside the picker, so a blur of the field is a click on something in it, not a Tab.\n const refPointer = useRef(false);\n // The day the field last agreed on with the page, so a change from outside is told from our own report of it.\n const refKnown = useRef<string | undefined>(current);\n // The refused text last reported (with its message), or null: told apart from a fresh refusal so it is reported once,\n // and so that emptying a field that held a refused text is reported although it held no day.\n const refRefused = useRef<string | null>(null);\n const [text, setText] = useState(current === undefined ? '' : formatDay(current, locale));\n const [problem, setProblem] = useState<TDateProblem | null>(null);\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n if (current !== refKnown.current) {\n refKnown.current = current;\n refRefused.current = null;\n setText(current === undefined ? '' : formatDay(current, locale));\n setProblem(null);\n }\n }, [current, locale]);\n\n const report = (day: string | undefined, state: TDatePickerState) => {\n refKnown.current = day;\n refRefused.current = state.kind === 'refused' ? `${state.text}\\u0000${state.problem}` : null;\n onValueChange(day, state);\n };\n\n /**\n * Reads the text: a day is reported and written back in full, anything else is said inline and reported as no day,\n * with the state saying whether the field is empty or holds text it refused.\n */\n const commit = (typed: string) => {\n const parsed = parseDateText(typed, locale);\n if (parsed.kind === 'empty') {\n setProblem(null);\n if (current !== undefined || refRefused.current !== null) {\n report(undefined, { kind: 'empty' });\n }\n return;\n }\n const found = parsed.kind === 'problem' ? parsed.problem : dayProblem(parsed.day, { min, max, disabledDates });\n if (found !== undefined) {\n setProblem(found);\n const message = problemText(found, labels, locale, { min, max }, pattern);\n if (current !== undefined || refRefused.current !== `${typed}\\u0000${message}`) {\n report(undefined, { kind: 'refused', text: typed, problem: message });\n }\n return;\n }\n if (parsed.kind === 'day') {\n setProblem(null);\n setText(formatDay(parsed.day, locale));\n if (parsed.day !== current || refRefused.current !== null) {\n report(parsed.day, { kind: 'day' });\n }\n }\n };\n\n const pick = (date: Date | undefined) => {\n const day = dateToDay(date);\n if (day === undefined) {\n return;\n }\n setProblem(null);\n setText(formatDay(day, locale));\n if (day !== current || refRefused.current !== null) {\n report(day, { kind: 'day' });\n }\n setOpen(false);\n };\n\n /**\n * Text is read as the person leaves the field. A day (or nothing) is taken at once; a message about text that is not a\n * day is shown too, unless they are only on the way to the calendar: a press of the mouse on the button, or focus\n * going into the calendar that Alt+ArrowDown opened, with the text half typed. Leaving the picker for anywhere else\n * reads whatever is typed.\n */\n const handleBlur = (event: FocusEvent<HTMLElement>) => {\n const next = event.relatedTarget as Element | null;\n const intoCalendar = next?.closest('[data-slot=\"popover-content\"]') != null;\n const intoButton = next?.closest('[data-slot=\"date-picker-trigger\"]') != null;\n const typed = refInput.current?.value ?? '';\n if (!(intoCalendar || intoButton || (next !== null && refField.current?.contains(next) === true))) {\n commit(typed);\n return;\n }\n if (event.target !== refInput.current) {\n return;\n }\n const onTheWay = intoCalendar || (intoButton && refPointer.current);\n const parsed = parseDateText(typed, locale);\n const usable = parsed.kind === 'empty' || (parsed.kind === 'day' && dayProblem(parsed.day, { min, max, disabledDates }) === undefined);\n if (usable || !onTheWay) {\n commit(typed);\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown' && event.altKey && !disabled) {\n event.preventDefault();\n setOpen(true);\n return;\n }\n // The field has no key handler of Base UI's to close the calendar, so the key would reach the Sheet or Dialog around\n // it and close that too. While the calendar is open it is this field's key; when it is closed it is the page's.\n if (event.key === 'Escape' && open) {\n event.preventDefault();\n event.stopPropagation();\n setOpen(false);\n return;\n }\n if (event.key === 'Enter') {\n const parsed = parseDateText(event.currentTarget.value, locale);\n if (parsed.kind !== 'empty' && (parsed.kind === 'problem' || dayProblem(parsed.day, { min, max, disabledDates }) !== undefined)) {\n event.preventDefault();\n }\n commit(event.currentTarget.value);\n }\n };\n\n const selected = dayToDate(current);\n const limits = { min, max };\n const invalid = problem !== null || ariaInvalid === true || ariaInvalid === 'true';\n const describedBy = [ariaDescribedBy, problem === null ? undefined : errorId].filter(Boolean).join(' ') || undefined;\n const isOff = (date: Date): boolean => {\n const day = dateToDay(date);\n return day !== undefined && dayProblem(day, { min, max, disabledDates }) !== undefined;\n };\n const today = new Date();\n const start = dayToDate(min);\n const end = dayToDate(max);\n const month = selected ?? (start !== undefined && today < start ? start : end !== undefined && today > end ? end : today);\n\n return (\n <div data-slot=\"date-picker\" className={cn('flex flex-col gap-1.5', className)}>\n <div\n ref={refField}\n className=\"relative\"\n onPointerDownCapture={() => {\n refPointer.current = true;\n }}\n onPointerUpCapture={() => {\n refPointer.current = false;\n }}\n >\n <Input\n ref={refInput}\n id={id}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n data-slot=\"date-picker-input\"\n value={text}\n placeholder={placeholder ?? pattern}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-invalid={invalid ? true : undefined}\n aria-describedby={describedBy}\n className=\"pr-9\"\n onChange={(event: ChangeEvent<HTMLInputElement>) => {\n setText(event.target.value);\n setProblem(null);\n }}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n />\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n data-slot=\"date-picker-trigger\"\n aria-label={labels.choose}\n disabled={disabled}\n className=\"absolute top-0.5 right-0.5\"\n onBlur={handleBlur}\n />\n }\n >\n <IconCalendar aria-hidden=\"true\" />\n </PopoverTrigger>\n <PopoverContent anchor={refField} align=\"start\" className=\"w-auto p-0\" finalFocus={refInput} onBlur={handleBlur}>\n <Calendar\n mode=\"single\"\n required\n autoFocus\n locale={calendarLocale}\n weekStartsOn={firstWeekdayOf(locale)}\n defaultMonth={month}\n startMonth={start}\n endMonth={end}\n selected={selected}\n disabled={isOff}\n onSelect={pick}\n />\n </PopoverContent>\n </Popover>\n </div>\n {problem !== null && (\n <p id={errorId} data-slot=\"date-picker-error\" role=\"alert\" className=\"text-sm text-destructive\">\n {problemText(problem, labels, locale, limits, pattern)}\n </p>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAyBA,IAAa,6BAAgD;CAC3D,QAAQ;CACR,SAAS,YAAY,mBAAmB,QAAQ;CAChD,SAAS;CACT,YAAY,QAAQ,UAAU,IAAI;CAClC,WAAW,QAAQ,UAAU,IAAI;CACjC,aAAa;AACf;AAoDA,IAAM,eACJ,SACA,QACA,QACA,QACA,YACW;CACX,QAAQ,SAAR;EACE,KAAK,UACH,OAAO,OAAO,OAAO,OAAO;EAC9B,KAAK,WACH,OAAO,OAAO;EAChB,KAAK,aACH,OAAO,OAAO,UAAU,OAAO,QAAQ,KAAA,IAAY,KAAK,UAAU,OAAO,KAAK,MAAM,CAAC;EACvF,KAAK,YACH,OAAO,OAAO,SAAS,OAAO,QAAQ,KAAA,IAAY,KAAK,UAAU,OAAO,KAAK,MAAM,CAAC;EACtF,SACE,OAAO,OAAO;CAClB;AACF;;;;;AAMA,IAAa,cAAc,EACzB,IACA,OACA,eACA,KACA,KACA,eACA,WAAW,OACX,SAAS,SACT,gBACA,aACA,cAAc,WACd,gBAAgB,aAChB,oBAAoB,iBACpB,QAAQ,gBACR,gBACsB;CACtB,MAAM,SAAS,eAAe;EAAE,GAAG;EAA4B,GAAG;CAAe,IAAI,CAAC,cAAc,CAAC;CACrG,MAAM,UAAU,cAAc,cAAc,MAAM,GAAG,CAAC,MAAM,CAAC;CAC7D,MAAM,UAAU,UAAU,KAAK,KAAA,IAAY;CAC3C,MAAM,UAAU,MAAM;CACtB,MAAM,WAAW,OAA8B,IAAI;CACnD,MAAM,WAAW,OAAgC,IAAI;CAErD,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,WAAW,OAA2B,OAAO;CAGnD,MAAM,aAAa,OAAsB,IAAI;CAC7C,MAAM,CAAC,MAAM,WAAW,SAAS,YAAY,KAAA,IAAY,KAAK,UAAU,SAAS,MAAM,CAAC;CACxF,MAAM,CAAC,SAAS,cAAc,SAA8B,IAAI;CAChE,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,gBAAgB;EACd,IAAI,YAAY,SAAS,SAAS;GAChC,SAAS,UAAU;GACnB,WAAW,UAAU;GACrB,QAAQ,YAAY,KAAA,IAAY,KAAK,UAAU,SAAS,MAAM,CAAC;GAC/D,WAAW,IAAI;EACjB;CACF,GAAG,CAAC,SAAS,MAAM,CAAC;CAEpB,MAAM,UAAU,KAAyB,UAA4B;EACnE,SAAS,UAAU;EACnB,WAAW,UAAU,MAAM,SAAS,YAAY,GAAG,MAAM,KAAK,QAAQ,MAAM,YAAY;EACxF,cAAc,KAAK,KAAK;CAC1B;;;;;CAMA,MAAM,UAAU,UAAkB;EAChC,MAAM,SAAS,cAAc,OAAO,MAAM;EAC1C,IAAI,OAAO,SAAS,SAAS;GAC3B,WAAW,IAAI;GACf,IAAI,YAAY,KAAA,KAAa,WAAW,YAAY,MAClD,OAAO,KAAA,GAAW,EAAE,MAAM,QAAQ,CAAC;GAErC;EACF;EACA,MAAM,QAAQ,OAAO,SAAS,YAAY,OAAO,UAAU,WAAW,OAAO,KAAK;GAAE;GAAK;GAAK;EAAc,CAAC;EAC7G,IAAI,UAAU,KAAA,GAAW;GACvB,WAAW,KAAK;GAChB,MAAM,UAAU,YAAY,OAAO,QAAQ,QAAQ;IAAE;IAAK;GAAI,GAAG,OAAO;GACxE,IAAI,YAAY,KAAA,KAAa,WAAW,YAAY,GAAG,MAAM,QAAQ,WACnE,OAAO,KAAA,GAAW;IAAE,MAAM;IAAW,MAAM;IAAO,SAAS;GAAQ,CAAC;GAEtE;EACF;EACA,IAAI,OAAO,SAAS,OAAO;GACzB,WAAW,IAAI;GACf,QAAQ,UAAU,OAAO,KAAK,MAAM,CAAC;GACrC,IAAI,OAAO,QAAQ,WAAW,WAAW,YAAY,MACnD,OAAO,OAAO,KAAK,EAAE,MAAM,MAAM,CAAC;EAEtC;CACF;CAEA,MAAM,QAAQ,SAA2B;EACvC,MAAM,MAAM,UAAU,IAAI;EAC1B,IAAI,QAAQ,KAAA,GACV;EAEF,WAAW,IAAI;EACf,QAAQ,UAAU,KAAK,MAAM,CAAC;EAC9B,IAAI,QAAQ,WAAW,WAAW,YAAY,MAC5C,OAAO,KAAK,EAAE,MAAM,MAAM,CAAC;EAE7B,QAAQ,KAAK;CACf;;;;;;;CAQA,MAAM,cAAc,UAAmC;EACrD,MAAM,OAAO,MAAM;EACnB,MAAM,eAAe,MAAM,QAAQ,iCAA+B,KAAK;EACvE,MAAM,aAAa,MAAM,QAAQ,qCAAmC,KAAK;EACzE,MAAM,QAAQ,SAAS,SAAS,SAAS;EACzC,IAAI,EAAE,gBAAgB,cAAe,SAAS,QAAQ,SAAS,SAAS,SAAS,IAAI,MAAM,OAAQ;GACjG,OAAO,KAAK;GACZ;EACF;EACA,IAAI,MAAM,WAAW,SAAS,SAC5B;EAEF,MAAM,WAAW,gBAAiB,cAAc,WAAW;EAC3D,MAAM,SAAS,cAAc,OAAO,MAAM;EAE1C,IADe,OAAO,SAAS,WAAY,OAAO,SAAS,SAAS,WAAW,OAAO,KAAK;GAAE;GAAK;GAAK;EAAc,CAAC,MAAM,KAAA,KAC9G,CAAC,UACb,OAAO,KAAK;CAEhB;CAEA,MAAM,iBAAiB,UAA2C;EAChE,IAAI,MAAM,QAAQ,eAAe,MAAM,UAAU,CAAC,UAAU;GAC1D,MAAM,eAAe;GACrB,QAAQ,IAAI;GACZ;EACF;EAGA,IAAI,MAAM,QAAQ,YAAY,MAAM;GAClC,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,QAAQ,KAAK;GACb;EACF;EACA,IAAI,MAAM,QAAQ,SAAS;GACzB,MAAM,SAAS,cAAc,MAAM,cAAc,OAAO,MAAM;GAC9D,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,aAAa,WAAW,OAAO,KAAK;IAAE;IAAK;IAAK;GAAc,CAAC,MAAM,KAAA,IACnH,MAAM,eAAe;GAEvB,OAAO,MAAM,cAAc,KAAK;EAClC;CACF;CAEA,MAAM,WAAW,UAAU,OAAO;CAClC,MAAM,SAAS;EAAE;EAAK;CAAI;CAC1B,MAAM,UAAU,YAAY,QAAQ,gBAAgB,QAAQ,gBAAgB;CAC5E,MAAM,cAAc,CAAC,iBAAiB,YAAY,OAAO,KAAA,IAAY,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,KAAA;CAC3G,MAAM,SAAS,SAAwB;EACrC,MAAM,MAAM,UAAU,IAAI;EAC1B,OAAO,QAAQ,KAAA,KAAa,WAAW,KAAK;GAAE;GAAK;GAAK;EAAc,CAAC,MAAM,KAAA;CAC/E;CACA,MAAM,wBAAQ,IAAI,KAAK;CACvB,MAAM,QAAQ,UAAU,GAAG;CAC3B,MAAM,MAAM,UAAU,GAAG;CACzB,MAAM,QAAQ,aAAa,UAAU,KAAA,KAAa,QAAQ,QAAQ,QAAQ,QAAQ,KAAA,KAAa,QAAQ,MAAM,MAAM;CAEnH,OACE,qBAAC,OAAD;EAAK,aAAU;EAAc,WAAW,GAAG,yBAAyB,SAAS;EAA7E,UAAA,CACE,qBAAC,OAAD;GACE,KAAK;GACL,WAAU;GACV,4BAA4B;IAC1B,WAAW,UAAU;GACvB;GACA,0BAA0B;IACxB,WAAW,UAAU;GACvB;GARF,UAAA,CAUE,oBAAC,OAAD;IACE,KAAK;IACD;IACJ,MAAK;IACL,WAAU;IACV,cAAa;IACb,aAAU;IACV,OAAO;IACP,aAAa,eAAe;IAClB;IACV,cAAY;IACZ,gBAAc,UAAU,OAAO,KAAA;IAC/B,oBAAkB;IAClB,WAAU;IACV,WAAW,UAAyC;KAClD,QAAQ,MAAM,OAAO,KAAK;KAC1B,WAAW,IAAI;IACjB;IACA,QAAQ;IACR,WAAW;GACZ,CAAA,GACD,qBAAC,SAAD;IAAe;IAAM,cAAc;IAAnC,UAAA,CACE,oBAAC,gBAAD;KACE,QACE,oBAAC,QAAD;MACE,SAAQ;MACR,MAAK;MACL,aAAU;MACV,cAAY,OAAO;MACT;MACV,WAAU;MACV,QAAQ;KACT,CAAA;KAGH,UAAA,oBAAC,cAAD,EAAc,eAAY,OAAQ,CAAA;IACpB,CAAA,GAChB,oBAAC,gBAAD;KAAgB,QAAQ;KAAU,OAAM;KAAQ,WAAU;KAAa,YAAY;KAAU,QAAQ;KACnG,UAAA,oBAAC,UAAD;MACE,MAAK;MACL,UAAA;MACA,WAAA;MACA,QAAQ;MACR,cAAc,eAAe,MAAM;MACnC,cAAc;MACd,YAAY;MACZ,UAAU;MACA;MACV,UAAU;MACV,UAAU;KACX,CAAA;IACa,CAAA,CACT;GACN,CAAA,CAAA;EACJ,CAAA,GAAA,YAAY,QACX,oBAAC,KAAD;GAAG,IAAI;GAAS,aAAU;GAAoB,MAAK;GAAQ,WAAU;GAClE,UAAA,YAAY,SAAS,QAAQ,QAAQ,QAAQ,OAAO;EACpD,CAAA,CAEF;;AAET"}
|