@placeholderco/placeholder-ui 1.0.11 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +270 -137
  99. package/dist/form/Autocomplete.svelte.d.ts +58 -10
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +124 -43
  103. package/dist/form/Checkbox.svelte.d.ts +26 -2
  104. package/dist/form/Chips.svelte +47 -17
  105. package/dist/form/Chips.svelte.d.ts +24 -3
  106. package/dist/form/ComboBox.svelte +30 -20
  107. package/dist/form/ComboBox.svelte.d.ts +1 -1
  108. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  109. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  110. package/dist/form/ComboBoxMulti.svelte +38 -24
  111. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  112. package/dist/form/CronBuilder.svelte +84 -67
  113. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  114. package/dist/form/DatePicker.svelte +177 -191
  115. package/dist/form/DatePicker.svelte.d.ts +17 -2
  116. package/dist/form/DateRangePicker.svelte +775 -0
  117. package/dist/form/DateRangePicker.svelte.d.ts +23 -0
  118. package/dist/form/DateTimePicker.svelte +168 -209
  119. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  120. package/dist/form/FormGroup.svelte +33 -10
  121. package/dist/form/FormGroup.svelte.d.ts +12 -2
  122. package/dist/form/Number.svelte +71 -29
  123. package/dist/form/Number.svelte.d.ts +39 -6
  124. package/dist/form/Radio.svelte +174 -0
  125. package/dist/form/Radio.svelte.d.ts +21 -0
  126. package/dist/form/RadioGroup.svelte +52 -138
  127. package/dist/form/RadioGroup.svelte.d.ts +19 -2
  128. package/dist/form/SegmentedControl.svelte +103 -57
  129. package/dist/form/SegmentedControl.svelte.d.ts +12 -2
  130. package/dist/form/Select.svelte +160 -229
  131. package/dist/form/Select.svelte.d.ts +35 -6
  132. package/dist/form/SelectMulti.svelte +166 -277
  133. package/dist/form/SelectMulti.svelte.d.ts +37 -6
  134. package/dist/form/Slider.svelte +185 -268
  135. package/dist/form/Slider.svelte.d.ts +33 -16
  136. package/dist/form/StringArrayBuilder.svelte +249 -0
  137. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  138. package/dist/form/Switch.svelte +164 -106
  139. package/dist/form/Switch.svelte.d.ts +22 -3
  140. package/dist/form/TextArea.svelte +80 -45
  141. package/dist/form/TextArea.svelte.d.ts +35 -10
  142. package/dist/form/Textbox.svelte +80 -47
  143. package/dist/form/Textbox.svelte.d.ts +41 -8
  144. package/dist/form/TimePicker.svelte +42 -47
  145. package/dist/form/TimePicker.svelte.d.ts +12 -2
  146. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  147. package/dist/form/comboBoxCore.svelte.js +243 -0
  148. package/dist/icon/Icon.svelte +8 -3
  149. package/dist/icon/Icon.svelte.d.ts +8 -3
  150. package/dist/icon/cube-send.svg +1 -0
  151. package/dist/icon/file-type-bmp.svg +1 -0
  152. package/dist/icon/file-type-csv.svg +1 -0
  153. package/dist/icon/file-type-doc.svg +1 -0
  154. package/dist/icon/file-type-docx.svg +1 -0
  155. package/dist/icon/file-type-jpg.svg +1 -0
  156. package/dist/icon/file-type-pdf.svg +1 -0
  157. package/dist/icon/file-type-png.svg +1 -0
  158. package/dist/icon/file-type-ppt.svg +1 -0
  159. package/dist/icon/file-type-svg.svg +1 -0
  160. package/dist/icon/file-type-txt.svg +1 -0
  161. package/dist/icon/file-type-xls.svg +1 -0
  162. package/dist/icon/file-type-zip.svg +1 -0
  163. package/dist/icon/index.d.ts +166 -145
  164. package/dist/icon/index.js +166 -146
  165. package/dist/icon/layout-sidebar-left-collapse.svg +1 -0
  166. package/dist/icon/layout-sidebar-left-expand.svg +1 -0
  167. package/dist/icon/logo-placeholder.svg +6 -0
  168. package/dist/icon/paperclip.svg +1 -0
  169. package/dist/icon/source-code.svg +1 -0
  170. package/dist/icon/sun-moon.svg +1 -0
  171. package/dist/icon/table.svg +6 -0
  172. package/dist/icon/thumb-down.svg +1 -0
  173. package/dist/icon/thumb-up.svg +1 -0
  174. package/dist/index.d.ts +93 -22
  175. package/dist/index.js +28 -27
  176. package/dist/layout/AppShell.svelte +12 -162
  177. package/dist/layout/AppShell.svelte.d.ts +6 -27
  178. package/dist/layout/CustomNavbar.svelte +9 -7
  179. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  180. package/dist/layout/Navbar.svelte +158 -110
  181. package/dist/layout/Navbar.svelte.d.ts +26 -12
  182. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  183. package/dist/layout/NavbarItemView.svelte +182 -0
  184. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  185. package/dist/layout/Sidenav.svelte +280 -341
  186. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  187. package/dist/models/ComboBoxItem.d.ts +2 -0
  188. package/dist/models/Employee.d.ts +53 -0
  189. package/dist/models/Employee.js +23 -0
  190. package/dist/models/Hyperlink.d.ts +3 -1
  191. package/dist/models/LinkCollectionItem.d.ts +3 -1
  192. package/dist/models/NavbarItem.d.ts +6 -2
  193. package/dist/theme.svelte.d.ts +0 -1
  194. package/dist/theme.svelte.js +46 -58
  195. package/dist/ui/Accordion.svelte +81 -242
  196. package/dist/ui/Accordion.svelte.d.ts +37 -14
  197. package/dist/ui/AccordionItem.svelte +303 -0
  198. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  199. package/dist/ui/ActionIcon.svelte +25 -21
  200. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  201. package/dist/ui/Badge.svelte +170 -161
  202. package/dist/ui/Badge.svelte.d.ts +16 -4
  203. package/dist/ui/Button.svelte +178 -205
  204. package/dist/ui/Button.svelte.d.ts +19 -2
  205. package/dist/ui/ButtonVariant.d.ts +15 -1
  206. package/dist/ui/ContextMenu.svelte +146 -0
  207. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  208. package/dist/ui/ContextMenuList.svelte +188 -0
  209. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  210. package/dist/ui/Dialog.svelte +183 -104
  211. package/dist/ui/Dialog.svelte.d.ts +21 -2
  212. package/dist/ui/Drawer.svelte +237 -184
  213. package/dist/ui/Drawer.svelte.d.ts +20 -2
  214. package/dist/ui/Dropdown.svelte +24 -14
  215. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  216. package/dist/ui/Dropzone.svelte +159 -57
  217. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  218. package/dist/ui/Link.svelte +12 -4
  219. package/dist/ui/Link.svelte.d.ts +8 -2
  220. package/dist/ui/Loader.svelte +23 -18
  221. package/dist/ui/Loader.svelte.d.ts +5 -2
  222. package/dist/ui/Logo.svelte +37 -0
  223. package/dist/ui/Logo.svelte.d.ts +16 -0
  224. package/dist/ui/MultiSortable.svelte +287 -0
  225. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  226. package/dist/ui/Pagination.svelte +12 -6
  227. package/dist/ui/Pagination.svelte.d.ts +6 -2
  228. package/dist/ui/Popover.svelte +43 -65
  229. package/dist/ui/Popover.svelte.d.ts +14 -5
  230. package/dist/ui/ProgressBar.svelte +169 -0
  231. package/dist/ui/ProgressBar.svelte.d.ts +30 -0
  232. package/dist/ui/ProgressBarVariant.d.ts +7 -0
  233. package/dist/ui/ProgressBarVariant.js +1 -0
  234. package/dist/ui/SegmentedControl.svelte +0 -0
  235. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  236. package/dist/ui/Sortable.svelte +163 -0
  237. package/dist/ui/Sortable.svelte.d.ts +47 -0
  238. package/dist/ui/Table.svelte +497 -56
  239. package/dist/ui/Table.svelte.d.ts +64 -2
  240. package/dist/ui/Tabs.svelte +22 -18
  241. package/dist/ui/Tabs.svelte.d.ts +6 -2
  242. package/dist/ui/ThemeSwitcher.svelte +26 -7
  243. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  244. package/dist/ui/Tooltip.svelte +312 -180
  245. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  246. package/dist/uiTheme.svelte.d.ts +86 -0
  247. package/dist/uiTheme.svelte.js +214 -0
  248. package/dist/util/DateFunctions.js +1 -1
  249. package/dist/util/DragDrop.d.ts +50 -0
  250. package/dist/util/DragDrop.js +154 -0
  251. package/dist/util/NavigateTo.js +2 -2
  252. package/dist/util/dayjs.d.ts +4 -0
  253. package/dist/util/dayjs.js +15 -0
  254. package/dist/util/interceptLinkClick.d.ts +13 -0
  255. package/dist/util/interceptLinkClick.js +33 -0
  256. package/package.json +66 -26
  257. package/dist/display/Alert.svelte +0 -179
  258. package/dist/display/Alert.svelte.d.ts +0 -13
  259. package/dist/form/FileInput.svelte +0 -235
  260. package/dist/form/FileInput.svelte.d.ts +0 -23
  261. package/dist/form/PasswordInput.svelte +0 -252
  262. package/dist/form/PasswordInput.svelte.d.ts +0 -33
  263. package/dist/form/Rating.svelte +0 -235
  264. package/dist/form/Rating.svelte.d.ts +0 -21
  265. package/dist/styles/components.css +0 -199
  266. package/dist/styles/dark.css +0 -146
  267. package/dist/styles/index.css +0 -116
  268. package/dist/styles/reset.css +0 -110
  269. package/dist/styles/semantic.css +0 -86
  270. package/dist/styles/tokens.css +0 -203
  271. package/dist/styles/utilities.css +0 -523
  272. package/dist/ui/Breadcrumbs.svelte +0 -131
  273. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  274. package/dist/ui/LoadingOverlay.svelte +0 -53
  275. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  276. package/dist/ui/Progress.svelte +0 -191
  277. package/dist/ui/Progress.svelte.d.ts +0 -15
  278. package/dist/ui/RingProgress.svelte +0 -141
  279. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  280. package/dist/ui/Skeleton.svelte +0 -85
  281. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  282. package/dist/ui/Stepper.svelte +0 -355
  283. package/dist/ui/Stepper.svelte.d.ts +0 -24
  284. package/dist/ui/Timeline.svelte +0 -225
  285. package/dist/ui/Timeline.svelte.d.ts +0 -23
  286. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  287. /package/dist/{actions → util}/ClickOutside.js +0 -0
  288. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  289. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -0,0 +1,775 @@
1
+ <script module lang="ts">
2
+ export interface DateRangePickerProps {
3
+ /** Label text displayed above the input */
4
+ label?: string;
5
+ /** Disable the date range picker */
6
+ disabled?: boolean;
7
+ /** Mark field as required (shows asterisk) */
8
+ required?: boolean;
9
+ /** CSS classes for the container element */
10
+ containerClass?: string;
11
+ /** Selected start date in ISO format (bindable) */
12
+ startDate?: string | undefined;
13
+ /** Selected end date in ISO format (bindable) */
14
+ endDate?: string | undefined;
15
+ /** Minimum selectable date in ISO format */
16
+ minDate?: string | undefined;
17
+ /** Maximum selectable date in ISO format */
18
+ maxDate?: string | undefined;
19
+ /** Callback when selected range changes */
20
+ onchange?: (startDate: string | undefined, endDate: string | undefined) => void;
21
+ }
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import FormGroup from './FormGroup.svelte';
26
+ import { fade } from 'svelte/transition';
27
+ import dayjs from '../util/dayjs.js';
28
+ import { clickOutside } from '../util/ClickOutside.js';
29
+ import ActionIcon from '../ui/ActionIcon.svelte';
30
+ import { iconChevronLeft, iconChevronRight, iconX } from '../icon/index.js';
31
+
32
+ let {
33
+ label = '',
34
+ disabled = false,
35
+ required = false,
36
+ containerClass = '',
37
+ startDate = $bindable(undefined),
38
+ endDate = $bindable(undefined),
39
+ minDate,
40
+ maxDate,
41
+ onchange
42
+ }: DateRangePickerProps = $props();
43
+
44
+ const days = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
45
+
46
+ const minDateParsed = $derived(minDate ? dayjs(minDate).startOf('date') : undefined);
47
+ const maxDateParsed = $derived(maxDate ? dayjs(maxDate).startOf('date') : undefined);
48
+
49
+ const todayDate = dayjs().startOf('date');
50
+
51
+ let selectedStart: dayjs.Dayjs | undefined = $state(undefined);
52
+ let selectedEnd: dayjs.Dayjs | undefined = $state(undefined);
53
+ let selectingEnd = $state(false);
54
+ let hoverDate: dayjs.Dayjs | undefined = $state(undefined);
55
+
56
+ $effect(() => {
57
+ const newStart = startDate ? dayjs(startDate).startOf('date') : undefined;
58
+ const newEnd = endDate ? dayjs(endDate).startOf('date') : undefined;
59
+ selectedStart = newStart;
60
+ selectedEnd = newEnd;
61
+ if (newStart) focusedMonth = newStart.startOf('month');
62
+ });
63
+
64
+ // svelte-ignore state_referenced_locally
65
+ let focusedMonth = $state((selectedStart ?? todayDate).startOf('month'));
66
+ let showCalendar = $state(false);
67
+ let showYearPicker = $state(false);
68
+ let dropdownPosition: 'above' | 'below' = $state('below');
69
+ let containerElement: HTMLElement | undefined = $state(undefined);
70
+
71
+ const yearRangeSize = 12;
72
+ let yearRangeStart = $state(Math.floor(dayjs().year() / yearRangeSize) * yearRangeSize);
73
+
74
+ // Effective range (confirmed + hover preview)
75
+ const effectiveStart = $derived.by(() => {
76
+ if (selectingEnd && hoverDate && selectedStart) {
77
+ return hoverDate.isBefore(selectedStart) ? hoverDate : selectedStart;
78
+ }
79
+ return selectedStart;
80
+ });
81
+
82
+ const effectiveEnd = $derived.by(() => {
83
+ if (selectingEnd && hoverDate && selectedStart) {
84
+ return hoverDate.isBefore(selectedStart) ? selectedStart : hoverDate;
85
+ }
86
+ return selectedEnd;
87
+ });
88
+
89
+ const displayText = $derived.by(() => {
90
+ const startStr = selectedStart ? selectedStart.format('D MMM YYYY') : '';
91
+ const endStr = selectedEnd ? selectedEnd.format('D MMM YYYY') : '';
92
+ if (startStr && endStr) return `${startStr} – ${endStr}`;
93
+ if (startStr && selectingEnd) return `${startStr} – ...`;
94
+ if (startStr) return `${startStr} – ...`;
95
+ return '';
96
+ });
97
+
98
+ function startOfWeek(date: dayjs.Dayjs) {
99
+ return date.day(date.day() === 0 ? -6 : 1);
100
+ }
101
+
102
+ function endOfWeek(date: dayjs.Dayjs) {
103
+ return date.day(date.day() === 6 ? 8 : 7);
104
+ }
105
+
106
+ function getFullWeekCount(month: dayjs.Dayjs) {
107
+ const start = startOfWeek(month.date(1));
108
+ const end = endOfWeek(month.date(month.daysInMonth()));
109
+ return Math.ceil(end.diff(start, 'day', true) / 7) * 7;
110
+ }
111
+
112
+ function isDateInRange(date: dayjs.Dayjs) {
113
+ if (minDateParsed && date.isBefore(minDateParsed)) return false;
114
+ if (maxDateParsed && date.isAfter(maxDateParsed)) return false;
115
+ return true;
116
+ }
117
+
118
+ type DisplayedDate = {
119
+ date: dayjs.Dayjs;
120
+ text: string;
121
+ color: 'normal' | 'red' | 'grey' | 'disabled';
122
+ isRangeStart: boolean;
123
+ isRangeEnd: boolean;
124
+ isInRange: boolean;
125
+ isDisabled: boolean;
126
+ };
127
+
128
+ function calculateDays(
129
+ effStart: dayjs.Dayjs | undefined,
130
+ effEnd: dayjs.Dayjs | undefined,
131
+ month: dayjs.Dayjs
132
+ ): DisplayedDate[] {
133
+ const length = getFullWeekCount(month);
134
+ const displayedDates: DisplayedDate[] = [];
135
+ const startDate = startOfWeek(month);
136
+
137
+ for (let i = 0; i < length; i++) {
138
+ const date = startDate.add(i, 'day');
139
+
140
+ let color: DisplayedDate['color'];
141
+ let isDisabled = false;
142
+
143
+ if (!isDateInRange(date)) {
144
+ color = 'disabled';
145
+ isDisabled = true;
146
+ } else if (!date.isSame(month, 'month')) {
147
+ color = 'grey';
148
+ } else if (date.day() === 0 || date.day() === 6) {
149
+ color = 'red';
150
+ } else {
151
+ color = 'normal';
152
+ }
153
+
154
+ const isRangeStart = effStart !== undefined && date.isSame(effStart, 'date');
155
+ const isRangeEnd = effEnd !== undefined && date.isSame(effEnd, 'date');
156
+
157
+ let isInRange = false;
158
+ if (effStart && effEnd) {
159
+ isInRange =
160
+ (date.isAfter(effStart, 'date') || date.isSame(effStart, 'date')) &&
161
+ (date.isBefore(effEnd, 'date') || date.isSame(effEnd, 'date'));
162
+ } else if (effStart) {
163
+ isInRange = date.isSame(effStart, 'date');
164
+ }
165
+
166
+ displayedDates.push({ date, text: date.date().toString(), color, isRangeStart, isRangeEnd, isInRange, isDisabled });
167
+ }
168
+
169
+ return displayedDates;
170
+ }
171
+
172
+ function buildCellClasses(d: DisplayedDate) {
173
+ const cls: string[] = ['day-cell'];
174
+ if (d.isInRange) cls.push('in-range');
175
+ if (d.isRangeStart) cls.push('range-start');
176
+ if (d.isRangeEnd) cls.push('range-end');
177
+ if (d.isRangeStart && d.isRangeEnd) cls.push('single-day');
178
+ return cls.join(' ');
179
+ }
180
+
181
+ function buildButtonClasses(d: DisplayedDate) {
182
+ const cls: string[] = ['day-button'];
183
+ if (d.isDisabled) {
184
+ cls.push('disabled');
185
+ } else if (d.color === 'red') {
186
+ cls.push('text-red');
187
+ } else if (d.color === 'grey') {
188
+ cls.push('text-grey');
189
+ }
190
+ if (d.isRangeStart || d.isRangeEnd) {
191
+ cls.push('selected');
192
+ } else if (d.date.isSame(todayDate, 'date') && !d.isDisabled) {
193
+ cls.push('today');
194
+ }
195
+ return cls.join(' ');
196
+ }
197
+
198
+ function handleDayClick(d: DisplayedDate, event: MouseEvent) {
199
+ if (d.isDisabled) {
200
+ event.stopPropagation();
201
+ return;
202
+ }
203
+ event.stopPropagation();
204
+
205
+ if (!selectingEnd) {
206
+ // First click: pick start, reset end
207
+ selectedStart = d.date;
208
+ selectedEnd = undefined;
209
+ startDate = d.date.format('YYYY-MM-DD');
210
+ endDate = undefined;
211
+ selectingEnd = true;
212
+ hoverDate = undefined;
213
+ } else {
214
+ // Second click: pick end (swap if needed)
215
+ let s = selectedStart!;
216
+ let e = d.date;
217
+ if (e.isBefore(s)) [s, e] = [e, s];
218
+
219
+ selectedStart = s;
220
+ selectedEnd = e;
221
+ startDate = s.format('YYYY-MM-DD');
222
+ endDate = e.format('YYYY-MM-DD');
223
+ selectingEnd = false;
224
+ hoverDate = undefined;
225
+ onchange?.(startDate, endDate);
226
+ showCalendar = false;
227
+ }
228
+ }
229
+
230
+ function clearRange(event: MouseEvent) {
231
+ event.stopPropagation();
232
+ selectedStart = undefined;
233
+ selectedEnd = undefined;
234
+ startDate = undefined;
235
+ endDate = undefined;
236
+ selectingEnd = false;
237
+ hoverDate = undefined;
238
+ onchange?.(undefined, undefined);
239
+ }
240
+
241
+ function incrementFocusedMonth(delta: number) {
242
+ focusedMonth = focusedMonth.add(delta, 'month');
243
+ }
244
+
245
+ function checkDropdownPosition() {
246
+ if (!containerElement) return;
247
+ const rect = containerElement.getBoundingClientRect();
248
+ const spaceBelow = window.innerHeight - rect.bottom;
249
+ const spaceAbove = rect.top;
250
+ dropdownPosition = spaceBelow < 340 && spaceAbove > spaceBelow ? 'above' : 'below';
251
+ }
252
+
253
+ function onClickOutside(event: MouseEvent) {
254
+ if (!showCalendar) return;
255
+ const target = event.target as HTMLElement;
256
+ if (!containerElement?.contains(target)) {
257
+ showCalendar = false;
258
+ if (selectingEnd) {
259
+ // Cancel incomplete selection
260
+ selectingEnd = false;
261
+ selectedStart = startDate ? dayjs(startDate).startOf('date') : undefined;
262
+ hoverDate = undefined;
263
+ }
264
+ }
265
+ }
266
+
267
+ function getYearRange() {
268
+ const years: number[] = [];
269
+ for (let i = 0; i < yearRangeSize; i++) years.push(yearRangeStart + i);
270
+ return years;
271
+ }
272
+
273
+ function selectYear(year: number) {
274
+ focusedMonth = focusedMonth.year(year);
275
+ showYearPicker = false;
276
+ }
277
+
278
+ function isYearInRange(year: number) {
279
+ if (minDateParsed && year < minDateParsed.year()) return false;
280
+ if (maxDateParsed && year > maxDateParsed.year()) return false;
281
+ return true;
282
+ }
283
+
284
+ function incrementYearRange(delta: number) {
285
+ yearRangeStart += delta * yearRangeSize;
286
+ }
287
+
288
+ function toggleYearPicker() {
289
+ if (!showYearPicker) {
290
+ yearRangeStart = Math.floor(focusedMonth.year() / yearRangeSize) * yearRangeSize;
291
+ }
292
+ showYearPicker = !showYearPicker;
293
+ }
294
+ </script>
295
+
296
+ <div class="drp-container {containerClass}" bind:this={containerElement}>
297
+ <FormGroup {label} {required} id="drp-{label}">
298
+ <div
299
+ class="drp-input-wrap"
300
+ class:drp-disabled={disabled}
301
+ class:drp-open={showCalendar}
302
+ role="button"
303
+ tabindex={disabled ? -1 : 0}
304
+ onclick={() => {
305
+ if (!disabled) {
306
+ checkDropdownPosition();
307
+ showCalendar = true;
308
+ }
309
+ }}
310
+ onkeydown={(e) => {
311
+ if (!disabled && (e.key === 'Enter' || e.key === ' ')) {
312
+ e.preventDefault();
313
+ checkDropdownPosition();
314
+ showCalendar = true;
315
+ }
316
+ }}
317
+ >
318
+ <span class="drp-display-text" class:drp-placeholder={!displayText}>
319
+ {displayText || 'Select date range'}
320
+ </span>
321
+
322
+ {#if displayText && !disabled}
323
+ <div class="drp-clear">
324
+ <ActionIcon
325
+ variant="secondary-subtle"
326
+ svg={iconX}
327
+ size="0.75rem"
328
+ onclick={clearRange}
329
+ />
330
+ </div>
331
+ {/if}
332
+
333
+ <div class="drp-calendar-anchor">
334
+ {#if showCalendar}
335
+ <div
336
+ class="calendar {dropdownPosition}"
337
+ transition:fade={{ duration: 150 }}
338
+ use:clickOutside={onClickOutside}
339
+ >
340
+ <!-- Month navigation -->
341
+ <div class="month-buttons">
342
+ {#if showYearPicker}
343
+ <ActionIcon svg={iconChevronLeft} size="1.5rem" onclick={() => incrementYearRange(-1)} />
344
+ <button class="month-label clickable" onclick={toggleYearPicker}>
345
+ {yearRangeStart} – {yearRangeStart + yearRangeSize - 1}
346
+ </button>
347
+ <ActionIcon svg={iconChevronRight} size="1.5rem" onclick={() => incrementYearRange(1)} />
348
+ {:else}
349
+ <ActionIcon svg={iconChevronLeft} size="1.5rem" onclick={() => incrementFocusedMonth(-1)} />
350
+ <button class="month-label clickable" onclick={toggleYearPicker}>
351
+ {focusedMonth.format('MMMM YYYY')}
352
+ </button>
353
+ <ActionIcon svg={iconChevronRight} size="1.5rem" onclick={() => incrementFocusedMonth(1)} />
354
+ {/if}
355
+ </div>
356
+
357
+ {#if selectingEnd}
358
+ <div class="selecting-hint">Click to select end date</div>
359
+ {/if}
360
+
361
+ {#if showYearPicker}
362
+ <div class="year-grid">
363
+ {#each getYearRange() as year}
364
+ <button
365
+ class="year-button"
366
+ class:selected={focusedMonth.year() === year}
367
+ class:current-year={todayDate.year() === year}
368
+ class:disabled={!isYearInRange(year)}
369
+ disabled={!isYearInRange(year)}
370
+ onclick={() => selectYear(year)}
371
+ >
372
+ {year}
373
+ </button>
374
+ {/each}
375
+ </div>
376
+ {:else}
377
+ <div class="days-header-bar">
378
+ {#each days as day}
379
+ <div class="day-label">{day}</div>
380
+ {/each}
381
+ </div>
382
+
383
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
384
+ <div
385
+ class="days-grid"
386
+ onmouseleave={() => { if (selectingEnd) hoverDate = undefined; }}
387
+ >
388
+ {#each calculateDays(effectiveStart, effectiveEnd, focusedMonth) as d, i (i)}
389
+ <div
390
+ class={buildCellClasses(d)}
391
+ onmouseenter={() => { if (selectingEnd && !d.isDisabled) hoverDate = d.date; }}
392
+ >
393
+ <button
394
+ class={buildButtonClasses(d)}
395
+ disabled={d.isDisabled}
396
+ onclick={(e) => handleDayClick(d, e)}
397
+ >
398
+ {d.text}
399
+ </button>
400
+ </div>
401
+ {/each}
402
+ </div>
403
+ {/if}
404
+ </div>
405
+ {/if}
406
+ </div>
407
+ </div>
408
+ </FormGroup>
409
+ </div>
410
+
411
+ <style>
412
+ .drp-container {
413
+ position: relative;
414
+ }
415
+
416
+ .drp-input-wrap {
417
+ position: relative;
418
+ display: flex;
419
+ align-items: center;
420
+ width: 100%;
421
+ font-size: 1rem;
422
+ line-height: 1.5rem;
423
+ padding: 0.25rem 0.5rem;
424
+ border: 1px solid var(--border-color);
425
+ background-color: var(--input-bg-color);
426
+ color: var(--text-color);
427
+ border-radius: 0.25rem;
428
+ box-sizing: border-box;
429
+ cursor: pointer;
430
+ user-select: none;
431
+ min-height: 2.125rem;
432
+ transition: border-color 0.15s ease;
433
+ }
434
+
435
+ .drp-input-wrap:focus {
436
+ outline: none;
437
+ border-color: var(--accent-color);
438
+ }
439
+
440
+ .drp-input-wrap.drp-open {
441
+ border-color: var(--accent-color);
442
+ }
443
+
444
+ .drp-input-wrap.drp-disabled {
445
+ opacity: 0.5;
446
+ cursor: not-allowed;
447
+ background: var(--border-color);
448
+ pointer-events: none;
449
+ }
450
+
451
+ .drp-display-text {
452
+ flex: 1;
453
+ white-space: nowrap;
454
+ overflow: hidden;
455
+ text-overflow: ellipsis;
456
+ padding-right: 1.5rem;
457
+ }
458
+
459
+ .drp-placeholder {
460
+ color: #9ca3af;
461
+ }
462
+
463
+ .drp-clear {
464
+ position: absolute;
465
+ right: 0.5rem;
466
+ top: 50%;
467
+ transform: translateY(-50%);
468
+ line-height: 0;
469
+ }
470
+
471
+ .drp-calendar-anchor {
472
+ position: absolute;
473
+ left: 0;
474
+ right: 0;
475
+ top: 100%;
476
+ }
477
+
478
+ /* ---- Calendar ---- */
479
+
480
+ .calendar {
481
+ width: 19rem;
482
+ text-align: center;
483
+ position: absolute;
484
+ z-index: 10;
485
+ background-color: var(--paper-body-bg);
486
+ border: 1px solid var(--border-color);
487
+ border-radius: 0.375rem;
488
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
489
+ padding: 0.5rem;
490
+ }
491
+
492
+ .calendar.below {
493
+ top: 0.25rem;
494
+ }
495
+
496
+ .calendar.above {
497
+ bottom: calc(100% + 0.25rem);
498
+ top: auto;
499
+ }
500
+
501
+ .month-buttons {
502
+ display: flex;
503
+ justify-content: space-between;
504
+ align-items: center;
505
+ padding: 0.5rem;
506
+ }
507
+
508
+ .month-label {
509
+ font-weight: 600;
510
+ color: var(--text-color);
511
+ font-size: 1.125rem;
512
+ background: none;
513
+ border: none;
514
+ cursor: default;
515
+ }
516
+
517
+ .month-label.clickable {
518
+ cursor: pointer;
519
+ padding: 0.25rem 0.5rem;
520
+ border-radius: 0.25rem;
521
+ transition: background-color 0.15s ease;
522
+ }
523
+
524
+ .month-label.clickable:hover {
525
+ background-color: rgba(var(--ui-primary-rgbc), 0.1);
526
+ }
527
+
528
+ .selecting-hint {
529
+ font-size: 0.75rem;
530
+ color: var(--ui-primary);
531
+ font-style: italic;
532
+ margin-bottom: 0.25rem;
533
+ opacity: 0.75;
534
+ }
535
+
536
+ /* ---- Year picker ---- */
537
+
538
+ .year-grid {
539
+ display: grid;
540
+ grid-template-columns: repeat(3, 1fr);
541
+ gap: 0.5rem;
542
+ margin-top: 0.5rem;
543
+ padding: 0.5rem;
544
+ }
545
+
546
+ .year-button {
547
+ color: var(--text-color);
548
+ padding: 0.75rem 0.5rem;
549
+ border-radius: 0.375rem;
550
+ border: none;
551
+ background: transparent;
552
+ cursor: pointer;
553
+ font-size: 0.875rem;
554
+ transition: all 0.15s ease;
555
+ }
556
+
557
+ .year-button:hover:not(.disabled) {
558
+ background-color: rgba(var(--ui-primary-rgbc), 0.1);
559
+ color: var(--ui-primary);
560
+ }
561
+
562
+ .year-button.current-year {
563
+ background-color: rgba(var(--ui-primary-rgbc), 0.15);
564
+ color: var(--ui-primary);
565
+ font-weight: 600;
566
+ }
567
+
568
+ .year-button.selected {
569
+ background-color: var(--ui-primary);
570
+ color: white;
571
+ font-weight: 600;
572
+ }
573
+
574
+ .year-button.disabled {
575
+ color: rgb(163 163 163) !important;
576
+ background-color: transparent !important;
577
+ cursor: not-allowed !important;
578
+ opacity: 0.5;
579
+ }
580
+
581
+ /* ---- Days grid ---- */
582
+
583
+ .days-header-bar {
584
+ display: grid;
585
+ grid-template-columns: repeat(7, 1fr);
586
+ margin-top: 0.5rem;
587
+ background-color: #f8f9fa;
588
+ border-radius: 0.25rem;
589
+ border: 1px solid var(--border-color);
590
+ }
591
+
592
+ .day-label {
593
+ color: #6b7280;
594
+ padding: 0.5rem;
595
+ font-size: 0.75rem;
596
+ font-weight: 600;
597
+ text-transform: uppercase;
598
+ text-align: center;
599
+ }
600
+
601
+ .days-grid {
602
+ display: grid;
603
+ grid-template-columns: repeat(7, 1fr);
604
+ margin-top: 0.5rem;
605
+ }
606
+
607
+ /* ---- Day cells (range background strip) ---- */
608
+
609
+ .day-cell {
610
+ position: relative;
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: center;
614
+ height: 2.375rem;
615
+ }
616
+
617
+ /* Background strip pseudo-element */
618
+ .day-cell.in-range::before {
619
+ content: '';
620
+ position: absolute;
621
+ top: 50%;
622
+ transform: translateY(-50%);
623
+ height: 2rem;
624
+ left: 0;
625
+ right: 0;
626
+ background-color: rgba(var(--ui-primary-rgbc), 0.12);
627
+ z-index: 0;
628
+ }
629
+
630
+ /* Start: strip runs from center to right edge */
631
+ .day-cell.in-range.range-start::before {
632
+ left: 50%;
633
+ }
634
+
635
+ /* End: strip runs from left edge to center */
636
+ .day-cell.in-range.range-end::before {
637
+ right: 50%;
638
+ }
639
+
640
+ /* Single day: no strip */
641
+ .day-cell.in-range.single-day::before {
642
+ display: none;
643
+ }
644
+
645
+ /* ---- Day buttons ---- */
646
+
647
+ .day-button {
648
+ position: relative;
649
+ z-index: 1;
650
+ color: var(--text-color);
651
+ width: 2.125rem;
652
+ height: 2.125rem;
653
+ border-radius: 50%;
654
+ border: none;
655
+ background: transparent;
656
+ cursor: pointer;
657
+ font-size: 0.875rem;
658
+ transition: background-color 0.15s ease, color 0.15s ease;
659
+ flex-shrink: 0;
660
+ display: flex;
661
+ align-items: center;
662
+ justify-content: center;
663
+ }
664
+
665
+ .day-button:hover:not(.disabled):not(.selected) {
666
+ background-color: rgba(var(--ui-primary-rgbc), 0.12);
667
+ color: var(--ui-primary);
668
+ }
669
+
670
+ .today {
671
+ background-color: rgba(var(--ui-primary-rgbc), 0.15);
672
+ color: var(--ui-primary);
673
+ font-weight: 600;
674
+ }
675
+
676
+ .selected {
677
+ background-color: var(--ui-primary);
678
+ color: white;
679
+ font-weight: 600;
680
+ }
681
+
682
+ .text-red {
683
+ color: rgb(248 113 113);
684
+ }
685
+
686
+ .text-grey {
687
+ color: rgb(163 163 163);
688
+ }
689
+
690
+ .disabled {
691
+ color: rgb(163 163 163) !important;
692
+ background-color: transparent !important;
693
+ cursor: not-allowed !important;
694
+ opacity: 0.5;
695
+ }
696
+
697
+ .disabled:hover {
698
+ background-color: transparent !important;
699
+ color: rgb(163 163 163) !important;
700
+ }
701
+
702
+ /* ---- Dark mode ---- */
703
+
704
+ :global(.dark) {
705
+ .days-header-bar {
706
+ background-color: #252529;
707
+ }
708
+
709
+ .day-label {
710
+ color: #d1d5db;
711
+ }
712
+
713
+ .day-button:hover:not(.disabled):not(.selected) {
714
+ background-color: rgba(var(--ui-accent-rgbc), 0.12);
715
+ color: var(--ui-accent);
716
+ }
717
+
718
+ .today {
719
+ background-color: rgba(var(--ui-accent-rgbc), 0.15);
720
+ color: var(--ui-accent);
721
+ }
722
+
723
+ .selected {
724
+ background-color: var(--ui-accent);
725
+ color: var(--ui-primary);
726
+ }
727
+
728
+ .day-cell.in-range::before {
729
+ background-color: rgba(var(--ui-accent-rgbc), 0.18);
730
+ }
731
+
732
+ .text-grey {
733
+ color: #6b7280;
734
+ }
735
+
736
+ .disabled {
737
+ color: #6b7280 !important;
738
+ background-color: transparent !important;
739
+ cursor: not-allowed !important;
740
+ opacity: 0.5;
741
+ }
742
+
743
+ .disabled:hover {
744
+ background-color: transparent !important;
745
+ color: #6b7280 !important;
746
+ }
747
+
748
+ .selecting-hint {
749
+ color: var(--ui-accent);
750
+ }
751
+
752
+ .month-label.clickable:hover {
753
+ background-color: rgba(var(--ui-accent-rgbc), 0.1);
754
+ }
755
+
756
+ .year-button:hover:not(.disabled) {
757
+ background-color: rgba(var(--ui-accent-rgbc), 0.1);
758
+ color: var(--ui-accent);
759
+ }
760
+
761
+ .year-button.current-year {
762
+ background-color: rgba(var(--ui-accent-rgbc), 0.15);
763
+ color: var(--ui-accent);
764
+ }
765
+
766
+ .year-button.selected {
767
+ background-color: var(--ui-accent);
768
+ color: var(--ui-primary);
769
+ }
770
+
771
+ .year-button.disabled {
772
+ color: #6b7280 !important;
773
+ }
774
+ }
775
+ </style>