@bagelink/vue 1.14.13 → 1.15.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 (238) hide show
  1. package/dist/components/AddressSearch.vue.d.ts +6 -7
  2. package/dist/components/Alert.vue.d.ts.map +1 -1
  3. package/dist/components/Avatar.vue.d.ts.map +1 -1
  4. package/dist/components/Badge.vue.d.ts.map +1 -1
  5. package/dist/components/Btn.vue.d.ts +1 -1
  6. package/dist/components/Btn.vue.d.ts.map +1 -1
  7. package/dist/components/Card.vue.d.ts.map +1 -1
  8. package/dist/components/Carousel.vue.d.ts +0 -11
  9. package/dist/components/Dropdown.vue.d.ts +0 -2
  10. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  11. package/dist/components/Filter.vue.d.ts +30 -0
  12. package/dist/components/Filter.vue.d.ts.map +1 -0
  13. package/dist/components/FilterQuery.vue.d.ts +8 -3
  14. package/dist/components/Image.vue.d.ts.map +1 -1
  15. package/dist/components/ImportData.vue.d.ts.map +1 -1
  16. package/dist/components/ListItem.vue.d.ts.map +1 -1
  17. package/dist/components/MapEmbed/Index.vue.d.ts.map +1 -1
  18. package/dist/components/Modal.vue.d.ts +0 -1
  19. package/dist/components/Pagination.vue.d.ts.map +1 -1
  20. package/dist/components/Pill.vue.d.ts.map +1 -1
  21. package/dist/components/QueryFilter.vue.d.ts +30 -0
  22. package/dist/components/QueryFilter.vue.d.ts.map +1 -0
  23. package/dist/components/Swiper.vue.d.ts +6 -12
  24. package/dist/components/Swiper.vue.d.ts.map +1 -1
  25. package/dist/components/Toast.vue.d.ts.map +1 -1
  26. package/dist/components/analytics/PieChart.vue.d.ts +2 -2
  27. package/dist/components/calendar/CalendarPopover.vue.d.ts +8 -4
  28. package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
  29. package/dist/components/calendar/CalendarTypes.d.ts +0 -10
  30. package/dist/components/calendar/Index.vue.d.ts +4 -20
  31. package/dist/components/calendar/views/WeekView.vue.d.ts +1 -9
  32. package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
  33. package/dist/components/form/index.d.ts.map +1 -1
  34. package/dist/components/form/inputs/ArrayInput.vue.d.ts +2 -4
  35. package/dist/components/form/inputs/CheckInput.vue.d.ts +1 -2
  36. package/dist/components/form/inputs/Checkbox.vue.d.ts.map +1 -1
  37. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +0 -54
  38. package/dist/components/form/inputs/ColorInput.vue.d.ts +1 -3
  39. package/dist/components/form/inputs/DateInput.vue.d.ts +1 -2
  40. package/dist/components/form/inputs/DatePicker.vue.d.ts +0 -1
  41. package/dist/components/form/inputs/EmailInput.vue.d.ts +2 -5
  42. package/dist/components/form/inputs/JSONInput.vue.d.ts +1 -2
  43. package/dist/components/form/inputs/MarkdownEditor.vue.d.ts +2 -7
  44. package/dist/components/form/inputs/NumberInput.vue.d.ts +1 -2
  45. package/dist/components/form/inputs/OTP.vue.d.ts +1 -2
  46. package/dist/components/form/inputs/PasswordInput.vue.d.ts +10 -16
  47. package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -3
  48. package/dist/components/form/inputs/RangeInput.vue.d.ts +1 -6
  49. package/dist/components/form/inputs/RichText/index.vue.d.ts +1 -2
  50. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  51. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
  52. package/dist/components/form/inputs/SelectBtn.vue.d.ts +2 -2
  53. package/dist/components/form/inputs/SelectInput.vue.d.ts +13 -20
  54. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  55. package/dist/components/form/inputs/SignaturePad.vue.d.ts +1 -6
  56. package/dist/components/form/inputs/TableField.vue.d.ts +1 -2
  57. package/dist/components/form/inputs/TelInput.vue.d.ts +1 -2
  58. package/dist/components/form/inputs/TextInput.vue.d.ts +2 -3
  59. package/dist/components/form/inputs/ToggleInput.vue.d.ts +1 -2
  60. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +6 -27
  61. package/dist/components/form/inputs/Upload/upload.d.ts +1 -1
  62. package/dist/components/form/inputs/index.d.ts +0 -1
  63. package/dist/components/index.d.ts +1 -3
  64. package/dist/components/index.d.ts.map +1 -1
  65. package/dist/components/layout/AppContent.vue.d.ts +1 -1
  66. package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
  67. package/dist/components/layout/AppLayout.vue.d.ts +0 -2
  68. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
  69. package/dist/components/layout/AppSidebar.vue.d.ts +1 -5
  70. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  71. package/dist/components/layout/Panel.vue.d.ts.map +1 -1
  72. package/dist/components/layout/Resizable.vue.d.ts.map +1 -1
  73. package/dist/components/layout/Skeleton.vue.d.ts.map +1 -1
  74. package/dist/components/layout/TabsNav.vue.d.ts +1 -12
  75. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  76. package/dist/components/layout/appLayoutContext.d.ts +24 -0
  77. package/dist/components/layout/appLayoutContext.d.ts.map +1 -0
  78. package/dist/components/layout/index.d.ts.map +1 -1
  79. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  80. package/dist/composables/index.d.ts.map +1 -1
  81. package/dist/composables/useDevice.d.ts.map +1 -1
  82. package/dist/composables/useEscapeKey.d.ts +12 -0
  83. package/dist/composables/useEscapeKey.d.ts.map +1 -0
  84. package/dist/composables/useSchemaField.d.ts.map +1 -1
  85. package/dist/composables/useTheme.d.ts.map +1 -1
  86. package/dist/dialog/Dialog.vue.d.ts.map +1 -1
  87. package/dist/dialog/DialogConfirm.vue.d.ts.map +1 -1
  88. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  89. package/dist/form-flow/MultiStepForm.vue.d.ts +1 -6
  90. package/dist/form-flow/form-flow.d.ts +1 -24
  91. package/dist/form-flow/form-flow.d.ts.map +1 -1
  92. package/dist/i18n/index.d.ts +0 -838
  93. package/dist/index.cjs +245 -222
  94. package/dist/index.d.ts +0 -2
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.mjs +42201 -51162
  97. package/dist/plugins/bagel.d.ts.map +1 -1
  98. package/dist/style.css +1 -2
  99. package/dist/types/BagelForm.d.ts +1 -10
  100. package/dist/types/BagelForm.d.ts.map +1 -1
  101. package/dist/types/BtnOptions.d.ts.map +1 -1
  102. package/dist/types/NavLink.d.ts +1 -2
  103. package/dist/types/TableSchema.d.ts.map +1 -1
  104. package/dist/types/index.d.ts +1 -2
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/utils/BagelFormUtils.d.ts +0 -1
  107. package/dist/utils/calendar/dateUtils.d.ts +2 -2
  108. package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
  109. package/dist/utils/constants.d.ts.map +1 -1
  110. package/dist/utils/date.d.ts +116 -0
  111. package/dist/utils/date.d.ts.map +1 -0
  112. package/dist/utils/fetch.d.ts +29 -0
  113. package/dist/utils/fetch.d.ts.map +1 -0
  114. package/dist/utils/index.d.ts +1 -1
  115. package/dist/utils/index.d.ts.map +1 -1
  116. package/dist/utils/string.d.ts +7 -0
  117. package/dist/utils/string.d.ts.map +1 -0
  118. package/dist/utils/useSearch.d.ts +1 -1
  119. package/package.json +3 -10
  120. package/src/components/AccordionItem.vue +5 -5
  121. package/src/components/Alert.vue +37 -16
  122. package/src/components/Avatar.vue +2 -1
  123. package/src/components/Badge.vue +145 -22
  124. package/src/components/BglVideo.vue +4 -4
  125. package/src/components/Btn.vue +81 -69
  126. package/src/components/Card.vue +7 -6
  127. package/src/components/Dropdown.vue +7 -14
  128. package/src/components/FieldSetVue.vue +2 -2
  129. package/src/components/FilterQuery.vue +3 -3
  130. package/src/components/Image.vue +5 -3
  131. package/src/components/JSONSchema.vue +4 -4
  132. package/src/components/JsonBuilder.vue +3 -3
  133. package/src/components/ListItem.vue +2 -4
  134. package/src/components/MapEmbed/Index.vue +18 -17
  135. package/src/components/NavBar.vue +2 -2
  136. package/src/components/Spreadsheet/Index.vue +4 -4
  137. package/src/components/Spreadsheet/SpreadsheetTable.vue +10 -10
  138. package/src/components/Swiper.vue +3 -1
  139. package/src/components/Toast.vue +57 -36
  140. package/src/components/calendar/CalendarPopover.vue +1 -1
  141. package/src/components/calendar/Index.vue +5 -5
  142. package/src/components/calendar/views/AgendaView.vue +2 -2
  143. package/src/components/calendar/views/DayView.vue +1 -1
  144. package/src/components/calendar/views/MonthView.vue +8 -8
  145. package/src/components/dataTable/DataTable.vue +68 -10
  146. package/src/components/form/index.ts +0 -4
  147. package/src/components/form/inputs/ArrayInput.vue +1 -1
  148. package/src/components/form/inputs/CheckInput.vue +6 -6
  149. package/src/components/form/inputs/Checkbox.vue +5 -4
  150. package/src/components/form/inputs/CodeEditor/Index.vue +1 -1
  151. package/src/components/form/inputs/ColorInput.vue +5 -5
  152. package/src/components/form/inputs/DatePicker.vue +3 -3
  153. package/src/components/form/inputs/EmailInput.vue +15 -15
  154. package/src/components/form/inputs/NumberInput.vue +11 -11
  155. package/src/components/form/inputs/OTP.vue +4 -4
  156. package/src/components/form/inputs/PasswordInput.vue +3 -3
  157. package/src/components/form/inputs/RadioGroup.vue +1 -1
  158. package/src/components/form/inputs/RichText/editor.css +4 -4
  159. package/src/components/form/inputs/RichText/index.vue +39 -39
  160. package/src/components/form/inputs/RichText/utils/media.ts +1 -92
  161. package/src/components/form/inputs/RichText/utils/table.ts +4 -4
  162. package/src/components/form/inputs/SelectBtn.vue +1 -1
  163. package/src/components/form/inputs/SelectInput.vue +16 -16
  164. package/src/components/form/inputs/SignaturePad.vue +6 -6
  165. package/src/components/form/inputs/TableField.vue +7 -7
  166. package/src/components/form/inputs/TelInput.vue +12 -12
  167. package/src/components/form/inputs/TextInput.vue +11 -11
  168. package/src/components/form/inputs/ToggleInput.vue +11 -11
  169. package/src/components/form/inputs/Upload/upload.css +16 -16
  170. package/src/components/index.ts +2 -9
  171. package/src/components/layout/AppContent.vue +5 -19
  172. package/src/components/layout/AppLayout.vue +47 -18
  173. package/src/components/layout/AppSidebar.vue +19 -36
  174. package/src/components/layout/BottomMenu.vue +1 -1
  175. package/src/components/layout/Resizable.vue +5 -2
  176. package/src/components/layout/Skeleton.vue +5 -4
  177. package/src/components/layout/TabsNav.vue +23 -23
  178. package/src/components/layout/appLayoutContext.ts +44 -0
  179. package/src/components/layout/index.ts +2 -0
  180. package/src/components/lightbox/Lightbox.vue +3 -9
  181. package/src/composables/index.ts +1 -0
  182. package/src/composables/useDevice.ts +2 -1
  183. package/src/composables/useEscapeKey.ts +56 -0
  184. package/src/composables/useSchemaField.ts +2 -17
  185. package/src/composables/useTheme.ts +23 -19
  186. package/src/form-flow/FormFlow.vue +2 -0
  187. package/src/form-flow/form-flow.ts +7 -0
  188. package/src/index.ts +0 -3
  189. package/src/plugins/bagel.ts +0 -15
  190. package/src/styles/app-layout.css +231 -0
  191. package/src/styles/appearance.css +179 -21
  192. package/src/styles/bagel.css +103 -97
  193. package/src/styles/buttons.css +8 -8
  194. package/src/styles/colors.css +0 -103
  195. package/src/styles/dark.css +25 -26
  196. package/src/styles/input-variants.css +11 -11
  197. package/src/styles/inputs.css +44 -61
  198. package/src/styles/layout.css +445 -1258
  199. package/src/styles/loginCard.css +1 -1
  200. package/src/styles/mobilLayout.css +153 -28
  201. package/src/styles/text.css +500 -1508
  202. package/src/styles/theme.css +199 -435
  203. package/src/styles/transitions.css +4 -4
  204. package/src/types/BagelForm.ts +46 -151
  205. package/src/types/BtnOptions.ts +5 -3
  206. package/src/types/TableSchema.ts +1 -0
  207. package/src/types/index.ts +0 -5
  208. package/src/utils/calendar/dateUtils.ts +2 -3
  209. package/src/utils/constants.ts +7 -0
  210. package/src/utils/date.ts +482 -0
  211. package/src/utils/fetch.ts +128 -0
  212. package/src/utils/index.ts +54 -3
  213. package/src/utils/sizeParsing.ts +5 -5
  214. package/src/utils/string.ts +56 -0
  215. package/vite.config.ts +5 -1
  216. package/bin/generateFormSchema.ts +0 -1035
  217. package/bin/utils.ts +0 -223
  218. package/src/components/Carousel.vue +0 -724
  219. package/src/components/ImportData.vue +0 -1749
  220. package/src/components/Modal.vue +0 -184
  221. package/src/components/ModalConfirm.vue +0 -42
  222. package/src/components/ModalForm.vue +0 -102
  223. package/src/components/Pill.vue +0 -149
  224. package/src/components/Slider.vue +0 -1446
  225. package/src/components/Title.vue +0 -23
  226. package/src/components/ToolBar.vue +0 -9
  227. package/src/components/form/BagelForm.vue +0 -219
  228. package/src/components/form/BglFieldSet.vue +0 -14
  229. package/src/components/form/BglMultiStepForm.vue +0 -469
  230. package/src/components/form/FieldArray.vue +0 -422
  231. package/src/components/form/useBagelFormState.ts +0 -76
  232. package/src/composables/useFormField.ts +0 -38
  233. package/src/dialog/DialogOLD.vue +0 -358
  234. package/src/plugins/modalTypes.ts +0 -61
  235. package/src/plugins/useModal.ts +0 -225
  236. package/src/styles/modal.css +0 -120
  237. package/src/styles/pillColors.css +0 -0
  238. package/src/utils/BagelFormUtils.ts +0 -684
@@ -0,0 +1,482 @@
1
+ import { getI18n } from '../i18n'
2
+
3
+ function getLocale(): string {
4
+ try {
5
+ const { locale } = getI18n().global
6
+ return typeof locale === 'string' ? locale : locale.value
7
+ } catch {
8
+ if (typeof navigator === 'undefined') return 'en-US'
9
+ return (navigator.languages && navigator.languages.length)
10
+ ? navigator.languages[0]
11
+ : navigator.language
12
+ }
13
+ }
14
+
15
+ // ─── Types ────────────────────────────────────────────────────────────────────
16
+
17
+ export type DateInput = Date | number | string
18
+
19
+ // ─── Date convention ────────────────────────────────────────────────────────
20
+ //
21
+ // Server → Client: UTC (defaults to UTC when no TZ info present)
22
+ // Client → Server: Local time with TZ offset, OR UTC with Z suffix
23
+ // Server accepts: Anything with TZ info; defaults to UTC when absent
24
+ // Client accepts: Anything with TZ info; defaults to UTC when absent
25
+ // Display: Always device-local time (browser's Intl / Date methods)
26
+ //
27
+ // ── utc() — parse server dates ──────────────────────────────────────────────
28
+ //
29
+ // utc(input) → Date (interpreted as UTC when no TZ info)
30
+ // utc.iso(input) → normalized ISO string with Z
31
+ // utc.date(input) → "YYYY-MM-DD" in UTC
32
+ // utc.ms(input) → epoch milliseconds
33
+ //
34
+ // Handles:
35
+ // • Missing "Z" suffix or TZ offset → assumes UTC
36
+ // • Space instead of "T" → normalizes
37
+ // • Strings with explicit TZ offset → respects it
38
+ // • Already a Date / number → passes through
39
+ //
40
+ // ── local() — format for server submission ──────────────────────────────────
41
+ //
42
+ // local.iso(date) → ISO string with local TZ offset, e.g. "2026-05-05T11:00:00.000+03:00"
43
+ // local.offset() → current device TZ offset string, e.g. "+03:00"
44
+ //
45
+
46
+ function _parseUTC(input: DateInput): Date {
47
+ if (input instanceof Date) return input
48
+ if (typeof input === 'number') return new Date(input)
49
+ // Normalize: "2026-05-05 08:00:00" → "2026-05-05T08:00:00Z"
50
+ let s = input.trim().replace(' ', 'T')
51
+ // If no TZ info at all, assume UTC
52
+ if (!s.endsWith('Z') && !s.includes('+') && !/[+-]\d{2}:\d{2}$/.test(s)) s += 'Z'
53
+ return new Date(s)
54
+ }
55
+
56
+ export const utc = Object.assign(_parseUTC, {
57
+ /** Parse and return normalized ISO string with Z */
58
+ iso(input: DateInput): string {
59
+ return _parseUTC(input).toISOString()
60
+ },
61
+ /** Parse and return UTC date-only string "YYYY-MM-DD" */
62
+ date(input: DateInput): string {
63
+ return _parseUTC(input).toISOString().slice(0, 10)
64
+ },
65
+ /** Parse and return epoch ms */
66
+ ms(input: DateInput): number {
67
+ return _parseUTC(input).getTime()
68
+ },
69
+ })
70
+
71
+ // ── local — format dates with device TZ for server submission ───────────────
72
+
73
+ function _tzOffset(date: Date): string {
74
+ const off = -date.getTimezoneOffset()
75
+ const sign = off >= 0 ? '+' : '-'
76
+ const h = String(Math.floor(Math.abs(off) / 60)).padStart(2, '0')
77
+ const m = String(Math.abs(off) % 60).padStart(2, '0')
78
+ return `${sign}${h}:${m}`
79
+ }
80
+
81
+ function _localISO(date: Date): string {
82
+ const y = date.getFullYear()
83
+ const mo = String(date.getMonth() + 1).padStart(2, '0')
84
+ const d = String(date.getDate()).padStart(2, '0')
85
+ const h = String(date.getHours()).padStart(2, '0')
86
+ const mi = String(date.getMinutes()).padStart(2, '0')
87
+ const s = String(date.getSeconds()).padStart(2, '0')
88
+ const ms = String(date.getMilliseconds()).padStart(3, '0')
89
+ return `${y}-${mo}-${d}T${h}:${mi}:${s}.${ms}${_tzOffset(date)}`
90
+ }
91
+
92
+ export const local = {
93
+ /** Format a Date as ISO string with local TZ offset, e.g. "2026-05-05T11:00:00.000+03:00" */
94
+ iso(date: Date = new Date()): string {
95
+ return _localISO(date)
96
+ },
97
+ /** Current device TZ offset string, e.g. "+03:00" or "-05:00" */
98
+ offset(date: Date = new Date()): string {
99
+ return _tzOffset(date)
100
+ },
101
+ }
102
+
103
+ export type TimeDeltaUnit = 'days' | 'hours' | 'minutes' | 'seconds' | 'weeks' | 'months' | 'years'
104
+
105
+ export interface TimeDeltaOptions {
106
+ days?: number
107
+ hours?: number
108
+ minutes?: number
109
+ seconds?: number
110
+ weeks?: number
111
+ months?: number
112
+ years?: number
113
+ }
114
+
115
+ export type DateDiffUnit = 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months'
116
+
117
+ export type DayFormatTypes = 'DD' | 'DDD' | 'DDDD'
118
+ export type MonthFormatTypes = 'MM' | 'MMM' | 'MMMM'
119
+ export type YearFormatTypes = 'YY' | 'YYYY'
120
+ export type HourFormatTypes = 'HH'
121
+ export type MinuteFormatTypes = 'mm'
122
+ export type SecondFormatTypes = 'ss'
123
+ export type MillisecondFormatTypes = 'sss'
124
+ export type AmPmFormatTypes = 'AmPm'
125
+ export type DateFormatSeparatorTypes = '/' | '-' | ' ' | ':' | '.'
126
+
127
+ export type CommonDateFormats
128
+ = | `${DayFormatTypes}${DateFormatSeparatorTypes}${MonthFormatTypes}${DateFormatSeparatorTypes}${YearFormatTypes}`
129
+ | `${MonthFormatTypes}${DateFormatSeparatorTypes}${YearFormatTypes}`
130
+ | 'DD.MM.YY' | 'DD.MM.YYYY' | 'DD/MM/YY' | 'DD/MM/YYYY'
131
+ | 'MM.DD.YY' | 'MM.DD.YYYY' | 'MM/DD/YY' | 'MM/DD/YYYY'
132
+ | 'YYYY-MM-DD' | 'YY-MM-DD'
133
+ | 'DD MMM YYYY' | 'DD MMMM YYYY'
134
+ | 'DDD, DD MMM' | 'DDDD, DD MMMM'
135
+ | 'MMM DD' | 'MMMM DD'
136
+
137
+ export type CommonTimeFormats
138
+ = | 'HH:mm' | 'HH:mm:ss' | 'HH:mm:ss:sss'
139
+ | 'HH:mm AmPm'
140
+
141
+ export type CommonDateTimeFormats
142
+ = | `${CommonDateFormats} ${CommonTimeFormats}`
143
+ | `${CommonTimeFormats}, ${CommonDateFormats}`
144
+ | 'YYYY-MM-DD HH:MM'
145
+
146
+ export type NamedFormats = 'ISO' | 'ISO8601' | 'UTC' | 'RFC2822' | 'RFC3339' | 'UNIX' | 'TIMESTAMP'
147
+
148
+ export type DateTimeAcceptedFormats
149
+ = | CommonDateFormats
150
+ | CommonTimeFormats
151
+ | CommonDateTimeFormats
152
+ | DayFormatTypes
153
+ | MonthFormatTypes
154
+ | YearFormatTypes
155
+ | HourFormatTypes
156
+ | MinuteFormatTypes
157
+ | SecondFormatTypes
158
+ | MillisecondFormatTypes
159
+ | NamedFormats
160
+
161
+ export interface FormatDateOptions extends Partial<Pick<Intl.DateTimeFormatOptions, 'hour12'>> {
162
+ format?: DateTimeAcceptedFormats
163
+ locale?: Intl.LocalesArgument
164
+ tz?: string
165
+ }
166
+
167
+ // ─── Time constants ────────────────────────────────────────────────────────────
168
+
169
+ export const MS = {
170
+ SECOND: 1_000,
171
+ MINUTE: 60_000,
172
+ HOUR: 3_600_000,
173
+ DAY: 86_400_000,
174
+ WEEK: 604_800_000,
175
+ } as const
176
+
177
+ export const { SECOND } = MS
178
+ export const { MINUTE } = MS
179
+ export const { HOUR } = MS
180
+ export const { DAY } = MS
181
+ export const { WEEK } = MS
182
+
183
+ // ─── Core date arithmetic ──────────────────────────────────────────────────────
184
+
185
+ export function timeDelta(date: DateInput, options: TimeDeltaOptions): Date {
186
+ const d = new Date(date)
187
+ if (options.days) d.setDate(d.getDate() + options.days)
188
+ if (options.hours) d.setHours(d.getHours() + options.hours)
189
+ if (options.minutes) d.setMinutes(d.getMinutes() + options.minutes)
190
+ if (options.seconds) d.setSeconds(d.getSeconds() + options.seconds)
191
+ if (options.weeks) d.setDate(d.getDate() + options.weeks * 7)
192
+ if (options.months) d.setMonth(d.getMonth() + options.months)
193
+ if (options.years) d.setFullYear(d.getFullYear() + options.years)
194
+ return d
195
+ }
196
+
197
+ const _MS_PER_UNIT: Record<DateDiffUnit, number> = {
198
+ seconds: 1_000,
199
+ minutes: 60_000,
200
+ hours: 3_600_000,
201
+ days: 86_400_000,
202
+ weeks: 604_800_000,
203
+ months: 0,
204
+ }
205
+
206
+ export function dateDiff(a: DateInput, b: DateInput, unit: DateDiffUnit = 'days'): number {
207
+ const da = new Date(a)
208
+ const db = new Date(b)
209
+ if (unit === 'months') {
210
+ const years = db.getFullYear() - da.getFullYear()
211
+ return years * 12 + (db.getMonth() - da.getMonth())
212
+ }
213
+ return Math.floor((db.getTime() - da.getTime()) / _MS_PER_UNIT[unit])
214
+ }
215
+
216
+ // ─── DateChain — fluent wrapper ────────────────────────────────────────────────
217
+
218
+ export class DateChain {
219
+ private _date: Date
220
+
221
+ constructor(date: DateInput) {
222
+ this._date = new Date(date)
223
+ }
224
+
225
+ add(n: number, unit: TimeDeltaUnit = 'days'): DateChain {
226
+ return new DateChain(timeDelta(this._date, { [unit]: n }))
227
+ }
228
+
229
+ subtract(n: number, unit: TimeDeltaUnit = 'days'): DateChain {
230
+ return new DateChain(timeDelta(this._date, { [unit]: -n }))
231
+ }
232
+
233
+ diff(other: DateInput, unit: DateDiffUnit = 'days'): number {
234
+ return dateDiff(this._date, other, unit)
235
+ }
236
+
237
+ isBefore(other: DateInput): boolean {
238
+ return this._date.getTime() < new Date(other).getTime()
239
+ }
240
+
241
+ isAfter(other: DateInput): boolean {
242
+ return this._date.getTime() > new Date(other).getTime()
243
+ }
244
+
245
+ isSameDay(other: DateInput): boolean {
246
+ const o = new Date(other)
247
+ return (
248
+ this._date.getFullYear() === o.getFullYear()
249
+ && this._date.getMonth() === o.getMonth()
250
+ && this._date.getDate() === o.getDate()
251
+ )
252
+ }
253
+
254
+ toDate(): Date {
255
+ return new Date(this._date)
256
+ }
257
+
258
+ valueOf(): number {
259
+ return this._date.getTime()
260
+ }
261
+
262
+ toString(): string {
263
+ return this._date.toISOString()
264
+ }
265
+ }
266
+
267
+ // ─── d — blended callable + static namespace ──────────────────────────────────
268
+
269
+ function _d(date?: DateInput): DateChain {
270
+ return new DateChain(date !== undefined ? date : new Date())
271
+ }
272
+
273
+ export const d = Object.assign(_d, {
274
+ // ── Relative to now ──────────────────────────────────────────
275
+ get now(): DateChain { return new DateChain(new Date()) },
276
+ get today(): DateChain {
277
+ const t = new Date()
278
+ t.setHours(0, 0, 0, 0)
279
+ return new DateChain(t)
280
+ },
281
+ get tomorrow(): DateChain { return new DateChain(timeDelta(new Date(), { days: 1 })) },
282
+ get yesterday(): DateChain { return new DateChain(timeDelta(new Date(), { days: -1 })) },
283
+ get nextWeek(): DateChain { return new DateChain(timeDelta(new Date(), { weeks: 1 })) },
284
+ get lastWeek(): DateChain { return new DateChain(timeDelta(new Date(), { weeks: -1 })) },
285
+
286
+ // ── Current month ────────────────────────────────────────────
287
+ get firstOfMonth(): DateChain {
288
+ const t = new Date()
289
+ return new DateChain(new Date(t.getFullYear(), t.getMonth(), 1))
290
+ },
291
+ get lastOfMonth(): DateChain {
292
+ const t = new Date()
293
+ return new DateChain(new Date(t.getFullYear(), t.getMonth() + 1, 0))
294
+ },
295
+
296
+ // ── Current week (Sunday start) ──────────────────────────────
297
+ get firstOfWeek(): DateChain {
298
+ const t = new Date()
299
+ return new DateChain(new Date(t.setDate(t.getDate() - t.getDay())))
300
+ },
301
+ get lastOfWeek(): DateChain {
302
+ const t = new Date()
303
+ return new DateChain(new Date(t.setDate(t.getDate() + (6 - t.getDay()))))
304
+ },
305
+
306
+ // ── Next month ───────────────────────────────────────────────
307
+ get firstOfNextMonth(): DateChain {
308
+ const t = new Date()
309
+ return new DateChain(new Date(t.getFullYear(), t.getMonth() + 1, 1))
310
+ },
311
+ get lastOfNextMonth(): DateChain {
312
+ const t = new Date()
313
+ return new DateChain(new Date(t.getFullYear(), t.getMonth() + 2, 0))
314
+ },
315
+
316
+ // ── Next year ────────────────────────────────────────────────
317
+ get firstOfNextYear(): DateChain {
318
+ return new DateChain(new Date(new Date().getFullYear() + 1, 0, 1))
319
+ },
320
+
321
+ // ── Methods ──────────────────────────────────────────────────
322
+ in: (n: number, unit: TimeDeltaUnit = 'days'): DateChain => new DateChain(timeDelta(new Date(), { [unit]: n })),
323
+ ago: (n: number, unit: TimeDeltaUnit = 'days'): DateChain => new DateChain(timeDelta(new Date(), { [unit]: -n })),
324
+ diff: (a: DateInput, b: DateInput, unit: DateDiffUnit = 'days'): number => dateDiff(a, b, unit),
325
+ isBefore: (a: DateInput, b: DateInput): boolean => new Date(a).getTime() < new Date(b).getTime(),
326
+ isAfter: (a: DateInput, b: DateInput): boolean => new Date(a).getTime() > new Date(b).getTime(),
327
+ isSameDay: (a: DateInput, b: DateInput): boolean => {
328
+ const da = new Date(a)
329
+ const db = new Date(b)
330
+ return (
331
+ da.getFullYear() === db.getFullYear()
332
+ && da.getMonth() === db.getMonth()
333
+ && da.getDate() === db.getDate()
334
+ )
335
+ },
336
+ })
337
+
338
+ // ─── Timezone + formatting ─────────────────────────────────────────────────────
339
+
340
+ export function handleTimezone(date: Date, intFmtOpt: Intl.DateTimeFormatOptions): Date {
341
+ if (intFmtOpt.timeZone === 'UTC') {
342
+ const utcDate = new Date(date.getTime())
343
+ utcDate.setMinutes(utcDate.getMinutes() + date.getTimezoneOffset())
344
+ return utcDate
345
+ }
346
+ try {
347
+ const formatter = new Intl.DateTimeFormat('en-US', { ...intFmtOpt, month: 'numeric' })
348
+ const formattedParts = formatter.formatToParts(date)
349
+ const parts: Record<string, number> = {}
350
+ for (const part of formattedParts) {
351
+ if (part.type !== 'literal' && part.type !== 'timeZoneName')
352
+ parts[part.type] = Number.parseInt(part.value, 10)
353
+ }
354
+ return new Date(
355
+ parts.year,
356
+ (parts.month || 1) - 1,
357
+ parts.day,
358
+ parts.hour || 0,
359
+ parts.minute || 0,
360
+ parts.second || 0,
361
+ )
362
+ } catch (error) {
363
+ console.warn(`Error handling timezone ${intFmtOpt.timeZone}:`, error)
364
+ return date
365
+ }
366
+ }
367
+
368
+ export function getDatePartsMap(date: Date, locale: Intl.LocalesArgument, intFmtOpt?: Intl.DateTimeFormatOptions) {
369
+ const d = intFmtOpt?.timeZone ? handleTimezone(date, intFmtOpt) : date
370
+ const year = d.getFullYear().toString()
371
+ return {
372
+ AmPm: d.toLocaleString(locale, { hour: 'numeric', hour12: true, minute: 'numeric' }).split(' ')[1],
373
+ DD: String(d.getDate()).padStart(2, '0'),
374
+ DDD: d.toLocaleString(locale, { weekday: 'short' }),
375
+ DDDD: d.toLocaleString(locale, { weekday: 'long' }),
376
+ HH: String(d.getHours()).padStart(2, '0'),
377
+ mm: String(d.getMinutes()).padStart(2, '0'),
378
+ MM: String(d.getMonth() + 1).padStart(2, '0'),
379
+ MMM: d.toLocaleString(locale, { month: 'short' }),
380
+ MMMM: d.toLocaleString(locale, { month: 'long' }),
381
+ ss: String(d.getSeconds()).padStart(2, '0'),
382
+ sss: String(d.getMilliseconds()).padStart(3, '0'),
383
+ YY: year.slice(-2),
384
+ YYYY: year,
385
+ }
386
+ }
387
+
388
+ // Precomputed token regex (module-level, avoid re-creating on every call)
389
+ const _sampleMap = getDatePartsMap(new Date(), 'en-US')
390
+ const _orderedTokens = (Object.keys(_sampleMap).sort((a, b) => b.length - a.length)) as (keyof typeof _sampleMap)[]
391
+ const _tokenRegex = new RegExp(_orderedTokens.join('|'), 'g')
392
+
393
+ export function formatDate(date?: DateInput, format?: DateTimeAcceptedFormats): string
394
+ export function formatDate(date?: DateInput, opts?: FormatDateOptions): string
395
+ export function formatDate(
396
+ date?: DateInput,
397
+ formatOrOpts?: DateTimeAcceptedFormats | FormatDateOptions,
398
+ ): string {
399
+ if (!date) return ''
400
+
401
+ let format: DateTimeAcceptedFormats | undefined
402
+ let locale: Intl.LocalesArgument | undefined
403
+ let timeZone: string | undefined
404
+ let rest: Partial<Pick<Intl.DateTimeFormatOptions, 'hour12'>> = {}
405
+
406
+ if (typeof formatOrOpts === 'string') {
407
+ format = formatOrOpts
408
+ } else if (formatOrOpts && typeof formatOrOpts === 'object') {
409
+ format = formatOrOpts.format
410
+ locale = formatOrOpts.locale
411
+ timeZone = formatOrOpts.tz
412
+ rest = formatOrOpts
413
+ }
414
+
415
+ if (format === 'ISO' || format === 'ISO8601') {
416
+ const parsed = new Date(date)
417
+ return Number.isNaN(parsed.getTime()) ? '' : parsed.toISOString()
418
+ }
419
+
420
+ format = format || 'DD.MM.YY'
421
+ locale = locale || getLocale()
422
+
423
+ try {
424
+ let parsed = new Date(date)
425
+
426
+ // Handle DD.MM.YY(YY) and DD/MM/YY(YY) strings that new Date() can't parse
427
+ if (typeof date === 'string' && Number.isNaN(parsed.getTime())) {
428
+ const parts = date.split(/[/.-]/)
429
+ if (parts.length === 3) {
430
+ const [d, m, y] = parts.map(p => Number.parseInt(p, 10))
431
+ if (!Number.isNaN(d) && !Number.isNaN(m) && !Number.isNaN(y)) {
432
+ const fullYear = y < 100 ? 2000 + y : y
433
+ parsed = new Date(fullYear, m - 1, d)
434
+ }
435
+ }
436
+ }
437
+
438
+ if (Number.isNaN(parsed.getTime())) {
439
+ console.warn('Invalid date provided to formatDate:', date)
440
+ return ''
441
+ }
442
+
443
+ const intFmtOpt: Intl.DateTimeFormatOptions = {
444
+ day: 'numeric',
445
+ hour: '2-digit',
446
+ hour12: rest.hour12 === undefined ? true : rest.hour12,
447
+ minute: '2-digit',
448
+ month: 'long',
449
+ second: '2-digit',
450
+ timeZone,
451
+ weekday: 'long',
452
+ year: 'numeric',
453
+ }
454
+
455
+ const datePartsMap = getDatePartsMap(parsed, locale, intFmtOpt)
456
+ const formattedParts = new Intl.DateTimeFormat(locale, intFmtOpt).formatToParts(parsed)
457
+ const partsMap: Partial<Record<Intl.DateTimeFormatPartTypes, string>> = {}
458
+
459
+ for (const part of formattedParts) {
460
+ if (part.type !== 'literal') partsMap[part.type] = part.value
461
+ }
462
+
463
+ if (partsMap.month) {
464
+ datePartsMap.MMM = partsMap.month.substring(0, 3)
465
+ datePartsMap.MMMM = partsMap.month
466
+ }
467
+ if (partsMap.weekday) {
468
+ datePartsMap.DDD = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone }).format(parsed)
469
+ datePartsMap.DDDD = partsMap.weekday
470
+ }
471
+ if (partsMap.dayPeriod) {
472
+ datePartsMap.AmPm = partsMap.dayPeriod
473
+ }
474
+
475
+ return format.replace(_tokenRegex, match => datePartsMap[match as keyof typeof datePartsMap])
476
+ } catch (error) {
477
+ console.warn(`Error formatting date: ${date} with format: ${format}`, error)
478
+ return ''
479
+ }
480
+ }
481
+
482
+ export const fmtDate = formatDate
@@ -0,0 +1,128 @@
1
+ import { fuzzyMatch } from './string'
2
+
3
+ // ─── Types ────────────────────────────────────────────────────────────────────
4
+
5
+ type RawResponse<T> = T[] | { data: T[] } | { items: T[] }
6
+
7
+ type ClientFetcher<T> = () => Promise<RawResponse<T>>
8
+ type ServerFetcher<T> = (query: string) => Promise<RawResponse<T>>
9
+ type Fetcher<T> = ClientFetcher<T> | ServerFetcher<T>
10
+
11
+ type Mapper<T, R> = (items: T[]) => R[]
12
+
13
+ export interface OptionItem {
14
+ label: string
15
+ value: string | number
16
+ [key: string]: any
17
+ }
18
+
19
+ export interface ToOptionsConfig<T> {
20
+ label: keyof T | ((item: T) => string)
21
+ value: keyof T
22
+ }
23
+
24
+ export interface NormalizedFetchResult<R> {
25
+ (filter?: string): Promise<R[]>
26
+ clearCache: () => void
27
+ }
28
+
29
+ // ─── toOptions ────────────────────────────────────────────────────────────────
30
+
31
+ function defaultLabel<T>(item: T): string {
32
+ const t = item as any
33
+ return t.label ?? t.name ?? t.title ?? String(t.id ?? '')
34
+ }
35
+
36
+ function defaultValue<T>(item: T): string | number {
37
+ const t = item as any
38
+ return t.value ?? t.key ?? t.id ?? ''
39
+ }
40
+
41
+ export function toOptions<T>(items: T[]): OptionItem[]
42
+ export function toOptions<T>(config: ToOptionsConfig<T>): Mapper<T, OptionItem>
43
+ export function toOptions<T>(
44
+ itemsOrConfig: T[] | ToOptionsConfig<T>,
45
+ ): OptionItem[] | Mapper<T, OptionItem> {
46
+ if (Array.isArray(itemsOrConfig)) {
47
+ return itemsOrConfig.map(item => ({
48
+ ...item as any,
49
+ label: defaultLabel(item),
50
+ value: defaultValue(item),
51
+ }))
52
+ }
53
+
54
+ const config = itemsOrConfig
55
+ return (items: T[]) => items.map(item => ({
56
+ ...item as any,
57
+ label: typeof config.label === 'function'
58
+ ? config.label(item)
59
+ : String(item[config.label] ?? ''),
60
+ value: item[config.value] as string | number,
61
+ }))
62
+ }
63
+
64
+ // ─── normalizedFetch ──────────────────────────────────────────────────────────
65
+
66
+ function unwrap<T>(response: RawResponse<T>): T[] {
67
+ if (Array.isArray(response)) return response
68
+ if ('data' in response) return response.data
69
+ if ('items' in response) return response.items
70
+ return []
71
+ }
72
+
73
+ export function normalizedFetch<T, R>(
74
+ fetcher: Fetcher<T>,
75
+ mapper: Mapper<T, R>,
76
+ options?: { timeout?: number },
77
+ ): NormalizedFetchResult<R> {
78
+ const timeout = options?.timeout ?? 10_000
79
+ const isServerSearch = fetcher.length > 0
80
+ let cache: R[] | null = null
81
+
82
+ async function fetchWithTimeout(query?: string): Promise<R[]> {
83
+ const call = isServerSearch
84
+ ? (fetcher as ServerFetcher<T>)(query ?? '')
85
+ : (fetcher as ClientFetcher<T>)()
86
+
87
+ const result = await Promise.race([
88
+ call,
89
+ new Promise<never>((_, reject) => setTimeout(() => { reject(new Error('Request timeout')) }, timeout),
90
+ ),
91
+ ])
92
+
93
+ return mapper(unwrap(result))
94
+ }
95
+
96
+ const getter = async (filter?: string): Promise<R[]> => {
97
+ try {
98
+ if (isServerSearch) {
99
+ return await fetchWithTimeout(filter)
100
+ }
101
+
102
+ if (!cache) {
103
+ cache = await fetchWithTimeout()
104
+ }
105
+
106
+ if (filter) {
107
+ return cache.filter((item) => {
108
+ const { label } = item as any
109
+ if (typeof label !== 'string') return true
110
+ return fuzzyMatch(label, filter).matches
111
+ }).sort((a, b) => {
112
+ const labelA = (a as any).label ?? ''
113
+ const labelB = (b as any).label ?? ''
114
+ return fuzzyMatch(labelB, filter!).score - fuzzyMatch(labelA, filter!).score
115
+ })
116
+ }
117
+
118
+ return cache
119
+ } catch (error) {
120
+ console.error('normalizedFetch error:', error)
121
+ return []
122
+ }
123
+ }
124
+
125
+ getter.clearCache = () => { cache = null }
126
+
127
+ return getter
128
+ }
@@ -172,6 +172,25 @@ export async function appendScript(src: string, options?: { id?: string }): Prom
172
172
  return loadingPromise
173
173
  }
174
174
 
175
+ /**
176
+ * Wait for a global (e.g. a CDN script's `window.X`) to become available.
177
+ * Resolves with the global value, or rejects after `timeout` ms (default 10s).
178
+ *
179
+ * @example
180
+ * await appendScript('https://unpkg.com/leaflet/dist/leaflet.js')
181
+ * const L = await awaitGlobal('L')
182
+ */
183
+ export async function awaitGlobal<T = any>(name: string, { timeout = 10_000, interval = 50 }: { timeout?: number, interval?: number } = {}): Promise<T> {
184
+ const start = Date.now()
185
+ while ((window as any)[name] === undefined) {
186
+ if (Date.now() - start > timeout) {
187
+ throw new Error(`awaitGlobal: window.${name} not available after ${timeout}ms`)
188
+ }
189
+ await sleep(interval)
190
+ }
191
+ return (window as any)[name] as T
192
+ }
193
+
175
194
  export function appendStyle(src: string): Promise<void> {
176
195
  return new Promise((resolve, reject) => {
177
196
  if (document.querySelector(`link[href="${src}"]`)) {
@@ -198,9 +217,6 @@ export function normalizeDimension(value: string | number | undefined, defaultMe
198
217
  return value
199
218
  }
200
219
 
201
- export * as bagelFormUtils from './BagelFormUtils'
202
- export { useForm } from './BagelFormUtils'
203
-
204
220
  export type { NormalizedOption } from './options'
205
221
  export { getOptionIcon, getOptionLabel, getOptionValue, normalizeOption } from './options'
206
222
 
@@ -291,5 +307,40 @@ export function downloadFile(source: string | Blob, fileName?: string) {
291
307
 
292
308
  export { defaultOptions, showdown } from './showdown'
293
309
 
310
+ // Note: the `DateInput` type from './date' is intentionally not re-exported
311
+ // here to avoid clashing with the DateInput component. Import it from
312
+ // '@bagelink/vue/src/utils/date' directly if needed.
313
+ export type {
314
+ CommonDateFormats,
315
+ CommonDateTimeFormats,
316
+ CommonTimeFormats,
317
+ DateDiffUnit,
318
+ DateTimeAcceptedFormats,
319
+ FormatDateOptions,
320
+ NamedFormats,
321
+ TimeDeltaOptions,
322
+ TimeDeltaUnit,
323
+ } from './date'
324
+ export {
325
+ d,
326
+ DateChain,
327
+ dateDiff,
328
+ DAY,
329
+ fmtDate,
330
+ formatDate,
331
+ getDatePartsMap,
332
+ handleTimezone,
333
+ HOUR,
334
+ local,
335
+ MINUTE,
336
+ MS,
337
+ SECOND,
338
+ timeDelta,
339
+ utc,
340
+ WEEK,
341
+ } from './date'
342
+ export * from './fetch'
343
+ export * from './string'
344
+
294
345
  export { formatString } from './strings'
295
346
  export { useDebounceFn } from '@vueuse/core'
@@ -7,11 +7,11 @@ export function standardSize(value: number | string | undefined, unit: UnitSize
7
7
  // Handle SizeType values
8
8
  if (typeof value === 'string') {
9
9
  const sizeTypeMap: Record<SizeType, number> = {
10
- xSmall: 16,
11
- small: 24,
12
- medium: 32,
13
- large: 48,
14
- xLarge: 64
10
+ xs: 16,
11
+ sm: 24,
12
+ md: 32,
13
+ lg: 48,
14
+ xl: 64
15
15
  }
16
16
 
17
17
  if (value in sizeTypeMap) {