@sincpro/mobile-ui 3.0.1 → 3.1.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 (308) hide show
  1. package/dist/Actions/Actions.Button.d.ts +6 -6
  2. package/dist/Actions/Actions.Button.js +650 -180
  3. package/dist/Actions/Actions.Dock.d.ts +1 -1
  4. package/dist/Actions/Actions.Dock.js +297 -18
  5. package/dist/Actions/Actions.FAB.js +100 -12
  6. package/dist/Actions/Actions.FilterButton.d.ts +17 -0
  7. package/dist/Actions/Actions.FilterButton.js +263 -33
  8. package/dist/Actions/Actions.IconButton.d.ts +1 -0
  9. package/dist/Actions/Actions.IconButton.js +196 -33
  10. package/dist/Actions/index.js +13 -13
  11. package/dist/Dialog/Confirmation.context.js +82 -16
  12. package/dist/Dialog/Dialog.ActionSheet.js +200 -41
  13. package/dist/Dialog/Dialog.Confirmation.js +168 -31
  14. package/dist/Dialog/Dialog.EditValue.js +449 -61
  15. package/dist/Dialog/Dialog.Sheet.d.ts +7 -3
  16. package/dist/Dialog/Dialog.Sheet.js +231 -15
  17. package/dist/Dialog/OverlayHost.d.ts +23 -0
  18. package/dist/Dialog/OverlayHost.js +196 -0
  19. package/dist/Dialog/SheetSurface.d.ts +24 -0
  20. package/dist/Dialog/SheetSurface.js +441 -0
  21. package/dist/Dialog/dialog-actions.d.ts +17 -0
  22. package/dist/Dialog/dialog-actions.js +59 -0
  23. package/dist/Dialog/index.d.ts +4 -1
  24. package/dist/Dialog/index.js +8 -7
  25. package/dist/Dialog/overlay.store.d.ts +24 -0
  26. package/dist/Dialog/overlay.store.js +59 -0
  27. package/dist/Dialog/requestConfirmation.js +20 -10
  28. package/dist/Display/Display.Accordion.js +185 -13
  29. package/dist/Display/Display.AmountSummary.js +206 -15
  30. package/dist/Display/Display.Avatar.js +93 -31
  31. package/dist/Display/Display.AvatarGroup.js +132 -17
  32. package/dist/Display/Display.Badge.d.ts +18 -2
  33. package/dist/Display/Display.Badge.js +117 -49
  34. package/dist/Display/Display.Card.js +163 -26
  35. package/dist/Display/Display.Carousel.js +459 -105
  36. package/dist/Display/Display.Chip.d.ts +4 -1
  37. package/dist/Display/Display.Chip.js +165 -31
  38. package/dist/Display/Display.CircleButton.d.ts +1 -0
  39. package/dist/Display/Display.CircleButton.js +86 -27
  40. package/dist/Display/Display.CopyableText.js +289 -66
  41. package/dist/Display/Display.CountRecords.js +60 -3
  42. package/dist/Display/Display.DataTable.js +123 -13
  43. package/dist/Display/Display.Date.js +243 -33
  44. package/dist/Display/Display.Divider.d.ts +10 -10
  45. package/dist/Display/Display.Divider.js +67 -20
  46. package/dist/Display/Display.ExpoImage.js +97 -8
  47. package/dist/Display/Display.GradientSurface.d.ts +9 -4
  48. package/dist/Display/Display.GradientSurface.js +196 -46
  49. package/dist/Display/Display.Icon.js +129 -15
  50. package/dist/Display/Display.IconBadge.js +95 -25
  51. package/dist/Display/Display.Image.d.ts +7 -1
  52. package/dist/Display/Display.Image.js +344 -39
  53. package/dist/Display/Display.KeyValue.js +90 -5
  54. package/dist/Display/Display.ListGroup.js +181 -9
  55. package/dist/Display/Display.ListItem.js +324 -28
  56. package/dist/Display/Display.Logo.js +84 -29
  57. package/dist/Display/Display.MediaCard.js +125 -4
  58. package/dist/Display/Display.MediaEmbed.d.ts +0 -5
  59. package/dist/Display/Display.MediaEmbed.js +170 -26
  60. package/dist/Display/Display.MenuButton.d.ts +2 -2
  61. package/dist/Display/Display.MenuButton.js +380 -94
  62. package/dist/Display/Display.MenuCard.js +122 -18
  63. package/dist/Display/Display.MenuGrid.js +43 -3
  64. package/dist/Display/Display.Monetary.js +90 -5
  65. package/dist/Display/Display.Pattern.js +306 -47
  66. package/dist/Display/Display.PriceTag.js +117 -14
  67. package/dist/Display/Display.ProductCard.js +154 -4
  68. package/dist/Display/Display.Rating.js +92 -11
  69. package/dist/Display/Display.SearchHistory.js +132 -6
  70. package/dist/Display/Display.SectionHeader.d.ts +5 -1
  71. package/dist/Display/Display.SectionHeader.js +125 -12
  72. package/dist/Display/Display.Stat.js +62 -3
  73. package/dist/Display/Display.StatCard.js +131 -7
  74. package/dist/Display/Display.StatusBadge.d.ts +29 -0
  75. package/dist/Display/Display.StatusBadge.js +68 -0
  76. package/dist/Display/Display.StatusDot.js +93 -18
  77. package/dist/Display/Display.Story.js +285 -35
  78. package/dist/Display/Display.StoryTray.js +354 -34
  79. package/dist/Display/Display.StoryViewer.js +571 -72
  80. package/dist/Display/Display.SwipeableRow.js +141 -20
  81. package/dist/Display/Display.Tag.d.ts +4 -1
  82. package/dist/Display/Display.Tag.js +66 -4
  83. package/dist/Display/Display.Timeline.js +102 -13
  84. package/dist/Display/Display.Tooltip.js +122 -26
  85. package/dist/Display/chevron.d.ts +6 -0
  86. package/dist/Display/chevron.js +32 -0
  87. package/dist/Display/count-badge.d.ts +21 -0
  88. package/dist/Display/count-badge.js +124 -0
  89. package/dist/Display/group-title.d.ts +10 -0
  90. package/dist/Display/group-title.js +53 -0
  91. package/dist/Display/icon-font.d.ts +3 -0
  92. package/dist/Display/icon-font.ios.d.ts +3 -0
  93. package/dist/Display/icon-font.ios.js +4 -0
  94. package/dist/Display/icon-font.js +7 -0
  95. package/dist/Display/icon-glyphs.d.ts +2 -0
  96. package/dist/Display/icon-glyphs.js +87 -0
  97. package/dist/Display/icon-map.d.ts +5 -1
  98. package/dist/Display/icon-map.js +340 -84
  99. package/dist/Display/icon-registry.d.ts +21 -0
  100. package/dist/Display/icon-registry.js +21 -0
  101. package/dist/Display/index.d.ts +6 -1
  102. package/dist/Display/index.js +53 -48
  103. package/dist/Display/pill.d.ts +16 -0
  104. package/dist/Display/pill.js +47 -0
  105. package/dist/Feedback/ErrorBoundary.js +35 -23
  106. package/dist/Feedback/Feedback.Banner.js +202 -24
  107. package/dist/Feedback/Feedback.DomainSplashScreen.js +246 -70
  108. package/dist/Feedback/Feedback.Empty.js +54 -3
  109. package/dist/Feedback/Feedback.EmptyState.js +114 -5
  110. package/dist/Feedback/Feedback.Error.d.ts +1 -1
  111. package/dist/Feedback/Feedback.Error.js +130 -5
  112. package/dist/Feedback/Feedback.Loading.d.ts +1 -1
  113. package/dist/Feedback/Feedback.Loading.js +70 -3
  114. package/dist/Feedback/Feedback.PermissionCard.js +161 -5
  115. package/dist/Feedback/Feedback.Progress.js +199 -41
  116. package/dist/Feedback/Feedback.ProgressCircle.js +184 -12
  117. package/dist/Feedback/Feedback.Skeleton.js +149 -36
  118. package/dist/Feedback/Feedback.Spinner.js +407 -79
  119. package/dist/Feedback/Feedback.Splash.js +240 -33
  120. package/dist/Feedback/Feedback.State.d.ts +17 -0
  121. package/dist/Feedback/Feedback.State.js +104 -0
  122. package/dist/Feedback/Toast.context.js +883 -167
  123. package/dist/Feedback/index.d.ts +3 -0
  124. package/dist/Feedback/index.js +15 -13
  125. package/dist/Form/Form.Checkbox.js +212 -22
  126. package/dist/Form/Form.ControlledCheckbox.d.ts +1 -1
  127. package/dist/Form/Form.ControlledCheckbox.js +46 -4
  128. package/dist/Form/Form.ControlledDropdown.d.ts +1 -1
  129. package/dist/Form/Form.ControlledDropdown.js +62 -4
  130. package/dist/Form/Form.ControlledInput.d.ts +4 -3
  131. package/dist/Form/Form.ControlledInput.js +72 -4
  132. package/dist/Form/Form.ControlledMonetaryInput.d.ts +6 -3
  133. package/dist/Form/Form.ControlledMonetaryInput.js +50 -4
  134. package/dist/Form/Form.ControlledRadioButton.d.ts +2 -2
  135. package/dist/Form/Form.ControlledRadioButton.js +45 -4
  136. package/dist/Form/Form.DatePicker.js +505 -107
  137. package/dist/Form/Form.Dropdown.js +303 -52
  138. package/dist/Form/Form.ErrorSummary.js +236 -44
  139. package/dist/Form/Form.Field.d.ts +24 -0
  140. package/dist/Form/Form.Field.js +55 -0
  141. package/dist/Form/Form.FractionalQuantityInput.js +508 -96
  142. package/dist/Form/Form.Input.d.ts +17 -3
  143. package/dist/Form/Form.Input.js +478 -29
  144. package/dist/Form/Form.KeyboardForm.d.ts +22 -8
  145. package/dist/Form/Form.KeyboardForm.js +83 -8
  146. package/dist/Form/Form.KeyboardToolbarHost.d.ts +14 -0
  147. package/dist/Form/Form.KeyboardToolbarHost.js +182 -0
  148. package/dist/Form/Form.MonetaryInput.js +164 -17
  149. package/dist/Form/Form.PickerField.d.ts +31 -0
  150. package/dist/Form/Form.PickerField.js +562 -0
  151. package/dist/Form/Form.PinInput.js +150 -15
  152. package/dist/Form/Form.QuantitySelector.js +229 -25
  153. package/dist/Form/Form.RadioButton.js +132 -24
  154. package/dist/Form/Form.Select.js +487 -56
  155. package/dist/Form/Form.Slider.js +133 -10
  156. package/dist/Form/Form.Switch.js +99 -4
  157. package/dist/Form/index.d.ts +11 -6
  158. package/dist/Form/index.js +26 -27
  159. package/dist/Form/input-text.d.ts +7 -0
  160. package/dist/Form/input-text.js +18 -0
  161. package/dist/Navigation/Navigation.AppBar.d.ts +3 -4
  162. package/dist/Navigation/Navigation.AppBar.js +535 -99
  163. package/dist/Navigation/Navigation.BottomNav.js +363 -98
  164. package/dist/Navigation/Navigation.CollapsingHeader.d.ts +28 -13
  165. package/dist/Navigation/Navigation.CollapsingHeader.js +369 -36
  166. package/dist/Navigation/Navigation.FloatingBar.js +71 -11
  167. package/dist/Navigation/Navigation.Menu.js +309 -95
  168. package/dist/Navigation/Navigation.MenuSection.js +80 -11
  169. package/dist/Navigation/Navigation.RowItem.d.ts +4 -2
  170. package/dist/Navigation/Navigation.RowItem.js +93 -8
  171. package/dist/Navigation/Navigation.Search.js +241 -27
  172. package/dist/Navigation/Navigation.SearchBar.d.ts +7 -1
  173. package/dist/Navigation/Navigation.SearchBar.js +218 -29
  174. package/dist/Navigation/Navigation.SearchSuggestions.js +77 -3
  175. package/dist/Navigation/Navigation.SegmentedControl.js +152 -14
  176. package/dist/Navigation/Navigation.Steps.js +105 -11
  177. package/dist/Navigation/Navigation.Tabs.js +449 -41
  178. package/dist/Navigation/index.d.ts +9 -4
  179. package/dist/Navigation/index.js +13 -13
  180. package/dist/Typography/Typography.Heading.js +33 -3
  181. package/dist/Typography/Typography.SelectableText.js +95 -7
  182. package/dist/Typography/Typography.Text.d.ts +4 -2
  183. package/dist/Typography/Typography.Text.js +217 -7
  184. package/dist/Typography/index.js +3 -3
  185. package/dist/branding.js +29 -13
  186. package/dist/icons/AddItemIcon.js +76 -2
  187. package/dist/icons/ArrangeCircle.js +82 -2
  188. package/dist/icons/ArrowDownIcon.js +63 -3
  189. package/dist/icons/ArrowUpIcon.js +63 -3
  190. package/dist/icons/BoxIcon.js +64 -2
  191. package/dist/icons/BoxTimeIcon.js +63 -2
  192. package/dist/icons/CalendarIcon.js +145 -2
  193. package/dist/icons/CardIcon.js +80 -2
  194. package/dist/icons/CashierIcon.js +39 -2
  195. package/dist/icons/CreditIcon.js +76 -3
  196. package/dist/icons/DuoIcon.js +75 -2
  197. package/dist/icons/HomeIcon.js +39 -2
  198. package/dist/icons/HomeIconOutlined.js +53 -2
  199. package/dist/icons/LogoIcon.js +87 -2
  200. package/dist/icons/MoneyReceiveIcon.js +86 -2
  201. package/dist/icons/PinIcon.js +39 -2
  202. package/dist/icons/ProfileIcon.js +47 -2
  203. package/dist/icons/SettingsIcon.js +114 -2
  204. package/dist/icons/SinpeIcon.js +60 -2
  205. package/dist/icons/ToggleOffIcon.js +53 -2
  206. package/dist/icons/WalletMoneyIcon.js +81 -2
  207. package/dist/index.d.ts +1 -0
  208. package/dist/index.js +8 -2
  209. package/dist/layouts/AuthContainer.d.ts +4 -5
  210. package/dist/layouts/AuthContainer.js +231 -7
  211. package/dist/layouts/BottomInset.js +4 -1
  212. package/dist/layouts/Container.d.ts +2 -2
  213. package/dist/layouts/Container.js +45 -3
  214. package/dist/layouts/GradientContainer.js +37 -5
  215. package/dist/layouts/PlainLayout.js +19 -3
  216. package/dist/layouts/ScrollContainer.d.ts +14 -9
  217. package/dist/layouts/ScrollContainer.js +125 -4
  218. package/dist/layouts/edges.d.ts +2 -0
  219. package/dist/layouts/edges.js +1 -0
  220. package/dist/primitives/AspectRatio.js +75 -3
  221. package/dist/primitives/Box.js +45 -3
  222. package/dist/primitives/Pressable.js +95 -5
  223. package/dist/primitives/Row.js +84 -13
  224. package/dist/primitives/Spacer.js +42 -5
  225. package/dist/primitives/Stack.js +66 -7
  226. package/dist/primitives/index.js +1 -1
  227. package/dist/theme/active.js +20 -18
  228. package/dist/theme/build.js +118 -87
  229. package/dist/theme/color.js +87 -87
  230. package/dist/theme/components/ThemedActivityIndicator.d.ts +2 -15
  231. package/dist/theme/components/ThemedActivityIndicator.js +35 -3
  232. package/dist/theme/components/index.js +1 -0
  233. package/dist/theme/contrast.js +32 -33
  234. package/dist/theme/css-vars.js +71 -75
  235. package/dist/theme/dark.js +116 -97
  236. package/dist/theme/default.js +116 -97
  237. package/dist/theme/define.js +16 -12
  238. package/dist/theme/index.d.ts +1 -0
  239. package/dist/theme/index.js +7 -6
  240. package/dist/theme/on-dark-surface.d.ts +12 -0
  241. package/dist/theme/on-dark-surface.js +13 -0
  242. package/dist/theme/theme.js +1 -0
  243. package/dist/theme/tw.js +3 -1
  244. package/dist/theme/types.js +0 -18
  245. package/dist/theme/typography.d.ts +1 -1
  246. package/dist/theme/typography.js +337 -303
  247. package/dist/theme/useTheme.js +12 -3
  248. package/dist/theme/useThemeVars.d.ts +9 -0
  249. package/dist/theme/useThemeVars.js +23 -0
  250. package/dist/theme/validate.js +175 -166
  251. package/dist/tokens/index.js +1 -1
  252. package/dist/tokens/motion.js +30 -13
  253. package/dist/utils/async.d.ts +14 -0
  254. package/dist/utils/async.js +22 -0
  255. package/dist/utils/criteria.js +66 -85
  256. package/dist/utils/date.d.ts +8 -2
  257. package/dist/utils/date.js +63 -50
  258. package/dist/utils/image.js +8 -8
  259. package/dist/utils/intl.d.ts +1 -0
  260. package/dist/utils/intl.js +13 -0
  261. package/dist/utils/keys.js +3 -5
  262. package/dist/utils/monetary.d.ts +1 -0
  263. package/dist/utils/monetary.js +28 -24
  264. package/dist/utils/progress.js +2 -3
  265. package/dist/utils/quantity.js +25 -26
  266. package/dist/utils/reorder.js +11 -20
  267. package/dist/utils/search.d.ts +4 -0
  268. package/dist/utils/search.js +12 -0
  269. package/dist/utils/serializer.js +38 -38
  270. package/dist/utils/useReducedMotion.js +32 -30
  271. package/dist/views/AppSplashView.js +117 -3
  272. package/dist/views/AuthFormView.d.ts +4 -5
  273. package/dist/views/AuthFormView.js +119 -11
  274. package/dist/views/FormViewV2.context.js +75 -23
  275. package/dist/views/FormViewV2.d.ts +4 -5
  276. package/dist/views/FormViewV2.js +658 -92
  277. package/dist/views/ListViewV2.context.js +91 -47
  278. package/dist/views/ListViewV2.d.ts +84 -43
  279. package/dist/views/ListViewV2.js +1350 -200
  280. package/dist/views/Wizard/Wizard.context.js +248 -84
  281. package/dist/views/Wizard/Wizard.js +260 -67
  282. package/dist/views/types/IListView.js +34 -31
  283. package/dist/widgets/CollectionOptions.js +557 -138
  284. package/dist/widgets/FilterBar.js +159 -7
  285. package/dist/widgets/FilterBuilder.js +375 -102
  286. package/dist/widgets/JSONViewer.js +275 -129
  287. package/dist/widgets/PromoBanner.js +336 -42
  288. package/dist/widgets/PromoModal.d.ts +2 -2
  289. package/dist/widgets/PromoModal.js +389 -62
  290. package/dist/widgets/QueryChoice.js +150 -8
  291. package/dist/widgets/QuickActions.js +120 -31
  292. package/dist/widgets/ReorderableList.js +399 -128
  293. package/dist/widgets/TimeZoneSelectorModal.js +458 -51
  294. package/dist/widgets/index.js +3 -3
  295. package/package.json +6 -1
  296. package/tailwind.preset.js +7 -5
  297. package/dist/Form/Form.InputField.d.ts +0 -10
  298. package/dist/Form/Form.InputField.js +0 -38
  299. package/dist/Form/Form.KeyboardToolbar.d.ts +0 -22
  300. package/dist/Form/Form.KeyboardToolbar.js +0 -66
  301. package/dist/Form/Form.PasswordInput.d.ts +0 -9
  302. package/dist/Form/Form.PasswordInput.js +0 -17
  303. package/dist/Form/Form.TextInput.d.ts +0 -8
  304. package/dist/Form/Form.TextInput.js +0 -8
  305. package/dist/primitives/BaseButton.d.ts +0 -150
  306. package/dist/primitives/BaseButton.js +0 -229
  307. package/dist/utils/useSheetTranslation.d.ts +0 -5
  308. package/dist/utils/useSheetTranslation.js +0 -23
@@ -1,99 +1,80 @@
1
1
  export function parseCriteriaDate(value) {
2
- if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
3
- return null;
4
- const date = new Date(`${value}T00:00:00Z`);
5
- if (!Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value)
6
- return null;
7
- return new Date(`${value}T12:00:00`);
2
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
3
+ const date = new Date(`${value}T00:00:00Z`);
4
+ if (!Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value) return null;
5
+ return new Date(`${value}T12:00:00`);
8
6
  }
9
7
  export function criteriaAt(root, path) {
10
- let node = root;
11
- for (const index of path) {
12
- if (node.kind !== "group" || !node.children[index])
13
- throw new Error("Invalid criteria path");
14
- node = node.children[index];
15
- }
16
- return node;
8
+ let node = root;
9
+ for (const index of path) {
10
+ if (node.kind !== "group" || !node.children[index]) throw new Error("Invalid criteria path");
11
+ node = node.children[index];
12
+ }
13
+ return node;
17
14
  }
18
15
  export function replaceCriteria(root, path, replacement) {
19
- if (path.length === 0) {
20
- if (replacement?.kind !== "group")
21
- throw new Error("Criteria root must remain a group");
22
- return replacement;
23
- }
24
- const [index, ...rest] = path;
25
- const child = root.children[index];
26
- if (!child)
27
- throw new Error("Invalid criteria path");
28
- if (rest.length && child.kind !== "group")
29
- throw new Error("Invalid criteria path");
30
- const next = rest.length && child.kind === "group"
31
- ? replaceCriteria(child, rest, replacement)
32
- : replacement;
33
- return {
34
- ...root,
35
- children: root.children.flatMap((item, position) => position !== index ? [item] : next ? [next] : []),
36
- };
16
+ if (path.length === 0) {
17
+ if (replacement?.kind !== "group") throw new Error("Criteria root must remain a group");
18
+ return replacement;
19
+ }
20
+ const [index, ...rest] = path;
21
+ const child = root.children[index];
22
+ if (!child) throw new Error("Invalid criteria path");
23
+ if (rest.length && child.kind !== "group") throw new Error("Invalid criteria path");
24
+ const next = rest.length && child.kind === "group" ? replaceCriteria(child, rest, replacement) : replacement;
25
+ return {
26
+ ...root,
27
+ children: root.children.flatMap((item, position) => position !== index ? [item] : next ? [next] : [])
28
+ };
37
29
  }
38
30
  export function describeCriteria(node, fields) {
39
- if (node.kind === "group") {
40
- if (!node.children.length)
41
- return "Sin condiciones";
42
- const join = { all: "Todas", any: "Cualquiera", none: "Ninguna" }[node.join];
43
- return `${join}: (${node.children.map((child) => describeCriteria(child, fields)).join("; ")})`;
44
- }
45
- const field = fields.find((item) => item.id === node.field);
46
- const operator = field?.operators.find((item) => item.id === node.operator);
47
- const values = node.values.map((value) => field?.options?.find((option) => option.value === value)?.label ?? String(value));
48
- return `${field?.label ?? node.field} ${operator?.label ?? node.operator}${values.length ? ` ${values.join(" — ")}` : ""}`;
31
+ if (node.kind === "group") {
32
+ if (!node.children.length) return "Sin condiciones";
33
+ const join = {
34
+ all: "Todas",
35
+ any: "Cualquiera",
36
+ none: "Ninguna"
37
+ }[node.join];
38
+ return `${join}: (${node.children.map(child => describeCriteria(child, fields)).join("; ")})`;
39
+ }
40
+ const field = fields.find(item => item.id === node.field);
41
+ const operator = field?.operators.find(item => item.id === node.operator);
42
+ const values = node.values.map(value => field?.options?.find(option => option.value === value)?.label ?? String(value));
43
+ return `${field?.label ?? node.field} ${operator?.label ?? node.operator}${values.length ? ` ${values.join(" — ")}` : ""}`;
49
44
  }
50
45
  export function validateCondition(condition, fields) {
51
- const field = fields.find((item) => item.id === condition.field);
52
- if (!field)
53
- return "El campo ya no está disponible.";
54
- const operator = field.operators.find((item) => item.id === condition.operator);
55
- if (!operator)
56
- return "El operador no está disponible para este campo.";
57
- const count = { none: 0, one: 1, range: 2 }[operator.arity];
58
- if (condition.values.length !== count)
59
- return "Completa los valores de la condición.";
60
- for (const value of condition.values) {
61
- if (field.type === "number" && (typeof value !== "number" || !Number.isFinite(value)))
62
- return "Introduce un número válido.";
63
- if (field.type === "text" && (typeof value !== "string" || !value.trim()))
64
- return "Introduce un texto.";
65
- if (field.type === "date" && !parseCriteriaDate(value))
66
- return "Introduce una fecha válida (AAAA-MM-DD).";
67
- if (field.type === "choice" && !field.options?.some((option) => option.value === value))
68
- return "Selecciona un valor disponible.";
69
- }
70
- if (operator.arity === "range" &&
71
- field.type === "number" &&
72
- Number(condition.values[0]) > Number(condition.values[1]))
73
- return "El inicio no puede superar el final.";
74
- if (operator.arity === "range" &&
75
- field.type === "date" &&
76
- String(condition.values[0]) > String(condition.values[1]))
77
- return "El inicio no puede superar el final.";
78
- return undefined;
46
+ const field = fields.find(item => item.id === condition.field);
47
+ if (!field) return "El campo ya no está disponible.";
48
+ const operator = field.operators.find(item => item.id === condition.operator);
49
+ if (!operator) return "El operador no está disponible para este campo.";
50
+ const count = {
51
+ none: 0,
52
+ one: 1,
53
+ range: 2
54
+ }[operator.arity];
55
+ if (condition.values.length !== count) return "Completa los valores de la condición.";
56
+ for (const value of condition.values) {
57
+ if (field.type === "number" && (typeof value !== "number" || !Number.isFinite(value))) return "Introduce un número válido.";
58
+ if (field.type === "text" && (typeof value !== "string" || !value.trim())) return "Introduce un texto.";
59
+ if (field.type === "date" && !parseCriteriaDate(value)) return "Introduce una fecha válida (AAAA-MM-DD).";
60
+ if (field.type === "choice" && !field.options?.some(option => option.value === value)) return "Selecciona un valor disponible.";
61
+ }
62
+ if (operator.arity === "range" && field.type === "number" && Number(condition.values[0]) > Number(condition.values[1])) return "El inicio no puede superar el final.";
63
+ if (operator.arity === "range" && field.type === "date" && String(condition.values[0]) > String(condition.values[1])) return "El inicio no puede superar el final.";
64
+ return undefined;
79
65
  }
80
66
  export function validateCriteria(group, fields) {
81
- for (const child of group.children) {
82
- if (child.kind === "group" && !child.children.length)
83
- return "Completa o elimina el grupo vacío.";
84
- const error = child.kind === "group"
85
- ? validateCriteria(child, fields)
86
- : validateCondition(child, fields);
87
- if (error)
88
- return error;
89
- }
90
- return undefined;
67
+ for (const child of group.children) {
68
+ if (child.kind === "group" && !child.children.length) return "Completa o elimina el grupo vacío.";
69
+ const error = child.kind === "group" ? validateCriteria(child, fields) : validateCondition(child, fields);
70
+ if (error) return error;
71
+ }
72
+ return undefined;
91
73
  }
92
74
  export function moveCollectionEntry(items, index, direction) {
93
- const target = index + direction;
94
- if (index < 0 || index >= items.length || target < 0 || target >= items.length)
95
- throw new Error("Invalid collection reorder");
96
- const next = [...items];
97
- [next[index], next[target]] = [next[target], next[index]];
98
- return next;
75
+ const target = index + direction;
76
+ if (index < 0 || index >= items.length || target < 0 || target >= items.length) throw new Error("Invalid collection reorder");
77
+ const next = [...items];
78
+ [next[index], next[target]] = [next[target], next[index]];
79
+ return next;
99
80
  }
@@ -2,14 +2,20 @@ interface FormatDateOptions {
2
2
  locale?: string | null;
3
3
  showTime?: boolean;
4
4
  }
5
+ /**
6
+ * Reads ISO strings, SQLite `YYYY-MM-DD HH:mm:ss` and Date instances as one instant.
7
+ * A timestamp without a zone is UTC. A date-only value is UTC midnight.
8
+ */
9
+ export declare function parseInstant(input: string | Date): Date | null;
5
10
  /**
6
11
  * Formats a date in the given timezone, respecting the granularity of the input.
7
12
  *
8
13
  * Detection rules:
9
14
  * 1. If `input` is a 'YYYY-MM-DD' string (date only) => interpreted as midnight (00:00:00)
10
15
  * in the target timezone and ONLY the date part is returned (no time).
11
- * 2. If `input` includes a time component (full ISO or Date) => converted to the target timezone
12
- * and shown as date + time (HH:MM) unless overridden via `options.showTime`.
16
+ * 2. If `input` includes a time component (ISO, `YYYY-MM-DD HH:mm:ss`, or Date) => that
17
+ * instant is formatted in the target timezone as date + time (HH:MM) unless overridden
18
+ * via `options.showTime`. A value without a zone is read as UTC.
13
19
  * 3. When `options.showTime` is `true` or `false` it overrides the auto-detection.
14
20
  * 4. If `timezone` is null/undefined or invalid, the string "Error de formateo" is returned.
15
21
  *
@@ -1,3 +1,4 @@
1
+ import { dateTimeFormat } from "./intl.js";
1
2
  import dayjs from "dayjs";
2
3
  import timezone from "dayjs/plugin/timezone";
3
4
  import utc from "dayjs/plugin/utc";
@@ -6,14 +7,31 @@ const DEFAULT_TIMEZONE = "America/Costa_Rica";
6
7
  const DEFAULT_LOCALE = "es-CR";
7
8
  dayjs.extend(utc);
8
9
  dayjs.extend(timezone);
10
+ /**
11
+ * Reads ISO strings, SQLite `YYYY-MM-DD HH:mm:ss` and Date instances as one instant.
12
+ * A timestamp without a zone is UTC. A date-only value is UTC midnight.
13
+ */
14
+ export function parseInstant(input) {
15
+ if (input instanceof Date) {
16
+ return Number.isNaN(input.getTime()) ? null : input;
17
+ }
18
+ const trimmed = input.trim();
19
+ if (!trimmed) return null;
20
+ const withT = trimmed.replace(/^(\d{4}-\d{2}-\d{2}) /, "$1T");
21
+ const zoned = /(?:Z|[+-]\d{2}:?\d{2})$/.test(withT);
22
+ const parsed = new Date(zoned || !withT.includes("T") ? withT : `${withT}Z`);
23
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
24
+ }
25
+
9
26
  /**
10
27
  * Formats a date in the given timezone, respecting the granularity of the input.
11
28
  *
12
29
  * Detection rules:
13
30
  * 1. If `input` is a 'YYYY-MM-DD' string (date only) => interpreted as midnight (00:00:00)
14
31
  * in the target timezone and ONLY the date part is returned (no time).
15
- * 2. If `input` includes a time component (full ISO or Date) => converted to the target timezone
16
- * and shown as date + time (HH:MM) unless overridden via `options.showTime`.
32
+ * 2. If `input` includes a time component (ISO, `YYYY-MM-DD HH:mm:ss`, or Date) => that
33
+ * instant is formatted in the target timezone as date + time (HH:MM) unless overridden
34
+ * via `options.showTime`. A value without a zone is read as UTC.
17
35
  * 3. When `options.showTime` is `true` or `false` it overrides the auto-detection.
18
36
  * 4. If `timezone` is null/undefined or invalid, the string "Error de formateo" is returned.
19
37
  *
@@ -46,39 +64,34 @@ dayjs.extend(timezone);
46
64
  * @returns Localized string, or "Error de formateo" if it can't be processed
47
65
  */
48
66
  export function formatDate(input, timezone, options) {
49
- // Use the default timezone when none is provided
50
- const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
51
- const fallbackLocale = DEFAULT_LOCALE.split("-")[0]; // "es" from "es-CR"
52
- const deviceLocale = getLocales()?.[0]?.languageTag ?? fallbackLocale;
53
- const usedLocale = options?.locale ?? deviceLocale;
54
- try {
55
- const rawStr = typeof input === "string" ? input : input.toISOString();
56
- const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(rawStr);
57
- // Auto-detect showTime when not passed explicitly
58
- const effectiveShowTime = options?.showTime !== undefined
59
- ? options.showTime
60
- : !isDateOnly && /T\d{2}:\d{2}/.test(rawStr); // has a time component
61
- const dateInstance = isDateOnly
62
- ? dayjs.tz(`${rawStr}T00:00:00`, effectiveTimezone)
63
- : dayjs.utc(rawStr).tz(effectiveTimezone);
64
- const dateInZone = dateInstance.toDate();
65
- const formatOptions = {
66
- year: "numeric",
67
- month: "long",
68
- day: "numeric",
69
- timeZone: effectiveTimezone,
70
- };
71
- if (effectiveShowTime) {
72
- formatOptions.hour = "2-digit";
73
- formatOptions.minute = "2-digit";
74
- }
75
- return new Intl.DateTimeFormat(usedLocale, formatOptions).format(dateInZone);
76
- }
77
- catch (error) {
78
- console.warn("Error formatting date:", error);
79
- return "Error de formateo";
67
+ // Use the default timezone when none is provided
68
+ const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
69
+ const fallbackLocale = DEFAULT_LOCALE.split("-")[0]; // "es" from "es-CR"
70
+ const deviceLocale = getLocales()?.[0]?.languageTag ?? fallbackLocale;
71
+ const usedLocale = options?.locale ?? deviceLocale;
72
+ const raw = typeof input === "string" ? input.trim() : input.toISOString();
73
+ const normalized = raw.replace(/^(\d{4}-\d{2}-\d{2}) /, "$1T");
74
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(normalized);
75
+ const effectiveShowTime = options?.showTime !== undefined ? options.showTime : !isDateOnly && /T\d{2}:\d{2}/.test(normalized);
76
+ try {
77
+ const date = isDateOnly ? dayjs.tz(`${normalized}T00:00:00`, effectiveTimezone).toDate() : parseInstant(input);
78
+ if (!date || Number.isNaN(date.getTime())) return "Error de formateo";
79
+ const formatOptions = {
80
+ year: "numeric",
81
+ month: "long",
82
+ day: "numeric",
83
+ timeZone: effectiveTimezone
84
+ };
85
+ if (effectiveShowTime) {
86
+ formatOptions.hour = "2-digit";
87
+ formatOptions.minute = "2-digit";
80
88
  }
89
+ return dateTimeFormat(usedLocale, formatOptions).format(date);
90
+ } catch {
91
+ return "Error de formateo";
92
+ }
81
93
  }
94
+
82
95
  /**
83
96
  * Formats a date using the default timezone automatically (Costa Rica).
84
97
  * Useful when the user's timezone isn't available.
@@ -88,8 +101,9 @@ export function formatDate(input, timezone, options) {
88
101
  * @returns Localized string in the Costa Rica timezone
89
102
  */
90
103
  export function formatDateWithDefaultTimezone(input, options) {
91
- return formatDate(input, DEFAULT_TIMEZONE, options);
104
+ return formatDate(input, DEFAULT_TIMEZONE, options);
92
105
  }
106
+
93
107
  /**
94
108
  * Returns the current date as YYYY-MM-DD in the given timezone.
95
109
  * Useful to build scheduling dates that respect the user's timezone.
@@ -98,15 +112,15 @@ export function formatDateWithDefaultTimezone(input, options) {
98
112
  * @returns Current date as YYYY-MM-DD in the given timezone
99
113
  */
100
114
  export function getCurrentDateInTimezone(timezone) {
101
- const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
102
- try {
103
- return dayjs().tz(effectiveTimezone).format("YYYY-MM-DD");
104
- }
105
- catch (error) {
106
- console.warn("Error getting current date in timezone:", error);
107
- return dayjs().format("YYYY-MM-DD"); // Fallback to local
108
- }
115
+ const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
116
+ try {
117
+ return dayjs().tz(effectiveTimezone).format("YYYY-MM-DD");
118
+ } catch (error) {
119
+ console.warn("Error getting current date in timezone:", error);
120
+ return dayjs().format("YYYY-MM-DD"); // Fallback to local
121
+ }
109
122
  }
123
+
110
124
  /**
111
125
  * Returns the current date-time as an ISO string in the given timezone.
112
126
  * Useful for timestamps that need to reflect the user's timezone.
@@ -115,12 +129,11 @@ export function getCurrentDateInTimezone(timezone) {
115
129
  * @returns Current date-time as an ISO string in the given timezone
116
130
  */
117
131
  export function getCurrentDateTimeInTimezone(timezone) {
118
- const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
119
- try {
120
- return dayjs().tz(effectiveTimezone).toISOString();
121
- }
122
- catch (error) {
123
- console.warn("Error getting current date-time in timezone:", error);
124
- return dayjs().toISOString(); // Fallback to local
125
- }
132
+ const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
133
+ try {
134
+ return dayjs().tz(effectiveTimezone).toISOString();
135
+ } catch (error) {
136
+ console.warn("Error getting current date-time in timezone:", error);
137
+ return dayjs().toISOString(); // Fallback to local
138
+ }
126
139
  }
@@ -1,13 +1,13 @@
1
1
  const sharedSourceKeys = new WeakMap();
2
2
  let nextSharedSourceKey = 0;
3
3
  export function imageSourceKey(source) {
4
- if (source && typeof source === "object" && "nativeRefType" in source) {
5
- let key = sharedSourceKeys.get(source);
6
- if (key === undefined) {
7
- key = nextSharedSourceKey++;
8
- sharedSourceKeys.set(source, key);
9
- }
10
- return `shared-image-${key}`;
4
+ if (source && typeof source === "object" && "nativeRefType" in source) {
5
+ let key = sharedSourceKeys.get(source);
6
+ if (key === undefined) {
7
+ key = nextSharedSourceKey++;
8
+ sharedSourceKeys.set(source, key);
11
9
  }
12
- return JSON.stringify(source);
10
+ return `shared-image-${key}`;
11
+ }
12
+ return JSON.stringify(source);
13
13
  }
@@ -0,0 +1 @@
1
+ export declare function dateTimeFormat(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
@@ -0,0 +1,13 @@
1
+ // Building an Intl formatter resolves locale and time-zone data, which is the slow
2
+ // part; format() on an existing one is cheap. Lists format the same few shapes
3
+ // over and over, so formatters are shared per locale and options.
4
+ const dateFormats = new Map();
5
+ export function dateTimeFormat(locale, options) {
6
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
7
+ let format = dateFormats.get(key);
8
+ if (!format) {
9
+ format = new Intl.DateTimeFormat(locale, options);
10
+ dateFormats.set(key, format);
11
+ }
12
+ return format;
13
+ }
@@ -1,7 +1,5 @@
1
1
  export function uniqueKeys(items, keyExtractor, component) {
2
- const keys = items.map(keyExtractor);
3
- if (keys.some((key) => typeof key !== "string" || !key.trim()) ||
4
- new Set(keys).size !== keys.length)
5
- throw new Error(`${component} requires unique, non-empty string keys.`);
6
- return keys;
2
+ const keys = items.map(keyExtractor);
3
+ if (keys.some(key => typeof key !== "string" || !key.trim()) || new Set(keys).size !== keys.length) throw new Error(`${component} requires unique, non-empty string keys.`);
4
+ return keys;
7
5
  }
@@ -1,3 +1,4 @@
1
1
  export declare function parseToMonetaryNumber(input?: string | number | null): number;
2
+ /** `1234.5` → `"1.234,50"`. Rounds half-up in decimal (Big), not in binary floating point. */
2
3
  export declare function formatTwoDecimals(value?: number | string | null): string;
3
4
  export declare function calculateDiscountedTotal(total: number, discount: number): number;
@@ -1,31 +1,35 @@
1
1
  import { Big } from "big.js";
2
2
  export function parseToMonetaryNumber(input) {
3
- if (input == null)
4
- return 0;
5
- const s = String(input).trim();
6
- if (!/[0-9]/.test(s))
7
- return 0;
8
- const cleaned = s.replace(/[^0-9.-]+/g, "");
9
- try {
10
- return Number(new Big(cleaned).toString());
11
- }
12
- catch {
13
- return 0;
14
- }
3
+ if (input == null) return 0;
4
+ // A finite number is already a number: skip the string round trip, which also
5
+ // misread exponent forms (1e21 → "121").
6
+ if (typeof input === "number") return Number.isFinite(input) ? input : 0;
7
+ const s = String(input).trim();
8
+ if (!/[0-9]/.test(s)) return 0;
9
+ const cleaned = s.replace(/[^0-9.-]+/g, "");
10
+ try {
11
+ return Number(new Big(cleaned).toString());
12
+ } catch {
13
+ return 0;
14
+ }
15
15
  }
16
+
17
+ // Building an Intl formatter is the expensive part (locale data lookup); format()
18
+ // is cheap. One shared instance for every amount the app renders.
19
+ const TWO_DECIMALS = new Intl.NumberFormat("de-DE", {
20
+ minimumFractionDigits: 2,
21
+ maximumFractionDigits: 2
22
+ });
23
+
24
+ /** `1234.5` → `"1.234,50"`. Rounds half-up in decimal (Big), not in binary floating point. */
16
25
  export function formatTwoDecimals(value) {
17
- const raw = parseToMonetaryNumber(value);
18
- const rounded = new Big(raw).toFixed(2);
19
- const num = Number(rounded);
20
- return new Intl.NumberFormat("de-DE", {
21
- minimumFractionDigits: 2,
22
- maximumFractionDigits: 2,
23
- }).format(num);
26
+ const rounded = new Big(parseToMonetaryNumber(value)).toFixed(2);
27
+ return TWO_DECIMALS.format(Number(rounded));
24
28
  }
25
29
  export function calculateDiscountedTotal(total, discount) {
26
- if (discount === 0) {
27
- return 0;
28
- }
29
- const result = (parseToMonetaryNumber(total) * discount) / 100;
30
- return parseToMonetaryNumber(result);
30
+ if (discount === 0) {
31
+ return 0;
32
+ }
33
+ const result = parseToMonetaryNumber(total) * discount / 100;
34
+ return parseToMonetaryNumber(result);
31
35
  }
@@ -1,5 +1,4 @@
1
1
  export function normalizeProgress(value) {
2
- if (!Number.isFinite(value))
3
- throw new RangeError("Progress value must be a finite number");
4
- return Math.max(0, Math.min(1, value));
2
+ if (!Number.isFinite(value)) throw new RangeError("Progress value must be a finite number");
3
+ return Math.max(0, Math.min(1, value));
5
4
  }
@@ -1,44 +1,43 @@
1
1
  import { Big } from "big.js";
2
+
2
3
  /** Redondea a la precisión indicada usando round half up */
3
4
  export function roundTo(value, precision) {
4
- if (isNaN(value))
5
- return 0;
6
- const factor = new Big(10).pow(precision);
7
- return Number(new Big(value).times(factor).round(0, 1).div(factor).toString());
5
+ if (isNaN(value)) return 0;
6
+ const factor = new Big(10).pow(precision);
7
+ return Number(new Big(value).times(factor).round(0, 1).div(factor).toString());
8
8
  }
9
+
9
10
  /** Normaliza la cantidad al múltiplo más cercano del incremento mínimo */
10
11
  export function normalizeQuantity(value, minIncrement, precision) {
11
- if (!isFinite(value))
12
- return 0;
13
- if (value < 0)
14
- value = 0;
15
- if (minIncrement <= 0)
16
- return roundTo(value, precision);
17
- const steps = Math.round(value / minIncrement);
18
- const normalized = steps * minIncrement;
19
- return roundTo(normalized, precision);
12
+ if (!isFinite(value)) return 0;
13
+ if (value < 0) value = 0;
14
+ if (minIncrement <= 0) return roundTo(value, precision);
15
+ const steps = Math.round(value / minIncrement);
16
+ const normalized = steps * minIncrement;
17
+ return roundTo(normalized, precision);
20
18
  }
19
+
21
20
  /** Convierte un valor a entero escalado según precisión para cálculos seguros */
22
21
  export function toScaledInt(value, precision) {
23
- if (!isFinite(value))
24
- return 0;
25
- const factor = Math.pow(10, precision);
26
- return Math.round(value * factor);
22
+ if (!isFinite(value)) return 0;
23
+ const factor = Math.pow(10, precision);
24
+ return Math.round(value * factor);
27
25
  }
28
26
  export function fromScaledInt(scaled, precision) {
29
- const factor = Math.pow(10, precision);
30
- return scaled / factor;
27
+ const factor = Math.pow(10, precision);
28
+ return scaled / factor;
31
29
  }
30
+
32
31
  /** Multiplicación segura evitando errores binarios típicos */
33
32
  export function safeMul(a, b, precision) {
34
- const factor = Math.pow(10, precision);
35
- return (Math.round(a * factor) * Math.round(b * factor)) / (factor * factor);
33
+ const factor = Math.pow(10, precision);
34
+ return Math.round(a * factor) * Math.round(b * factor) / (factor * factor);
36
35
  }
36
+
37
37
  /** Valida si un valor es múltiplo del incremento mínimo dentro de la precisión */
38
38
  export function isMultipleOfIncrement(value, minIncrement, precision) {
39
- if (minIncrement <= 0)
40
- return true;
41
- const scaledValue = toScaledInt(value, precision + 3); // extra precisión para reducir error
42
- const scaledInc = toScaledInt(minIncrement, precision + 3);
43
- return scaledValue % scaledInc === 0;
39
+ if (minIncrement <= 0) return true;
40
+ const scaledValue = toScaledInt(value, precision + 3); // extra precisión para reducir error
41
+ const scaledInc = toScaledInt(minIncrement, precision + 3);
42
+ return scaledValue % scaledInc === 0;
44
43
  }
@@ -1,29 +1,20 @@
1
1
  import { uniqueKeys } from "./keys.js";
2
2
  export function reorderKeys(items, keyExtractor) {
3
- return uniqueKeys(items, keyExtractor, "ReorderableList");
3
+ return uniqueKeys(items, keyExtractor, "ReorderableList");
4
4
  }
5
5
  export function sameOrder(left, right) {
6
- return left.length === right.length && left.every((key, index) => key === right[index]);
6
+ return left.length === right.length && left.every((key, index) => key === right[index]);
7
7
  }
8
8
  export function moveReorderItem(items, fromIndex, toIndex) {
9
- if (!Number.isInteger(fromIndex) ||
10
- !Number.isInteger(toIndex) ||
11
- fromIndex < 0 ||
12
- fromIndex >= items.length ||
13
- toIndex < 0 ||
14
- toIndex >= items.length)
15
- throw new Error("ReorderableList received an invalid move.");
16
- const next = [...items];
17
- const [item] = next.splice(fromIndex, 1);
18
- next.splice(toIndex, 0, item);
19
- return next;
9
+ if (!Number.isInteger(fromIndex) || !Number.isInteger(toIndex) || fromIndex < 0 || fromIndex >= items.length || toIndex < 0 || toIndex >= items.length) throw new Error("ReorderableList received an invalid move.");
10
+ const next = [...items];
11
+ const [item] = next.splice(fromIndex, 1);
12
+ next.splice(toIndex, 0, item);
13
+ return next;
20
14
  }
21
15
  export function applyReorder(items, keyExtractor, order) {
22
- const keys = reorderKeys(items, keyExtractor);
23
- const indices = new Map(keys.map((key, index) => [key, index]));
24
- if (order.length !== keys.length ||
25
- new Set(order).size !== order.length ||
26
- order.some((key) => !indices.has(key)))
27
- throw new Error("ReorderableList received an order that does not match the current items.");
28
- return order.map((key) => items[indices.get(key)]);
16
+ const keys = reorderKeys(items, keyExtractor);
17
+ const indices = new Map(keys.map((key, index) => [key, index]));
18
+ if (order.length !== keys.length || new Set(order).size !== order.length || order.some(key => !indices.has(key))) throw new Error("ReorderableList received an order that does not match the current items.");
19
+ return order.map(key => items[indices.get(key)]);
29
20
  }
@@ -0,0 +1,4 @@
1
+ /** Lowercase, no accents, single spaces: " Páez Núñez " -> "paez nunez". */
2
+ export declare function searchKey(text: string): string;
3
+ /** True when every word of the query appears in some field, ignoring accents and case. */
4
+ export declare function matchesSearch(query: string, ...fields: (string | null | undefined)[]): boolean;
@@ -0,0 +1,12 @@
1
+ /** Lowercase, no accents, single spaces: " Páez Núñez " -> "paez nunez". */
2
+ export function searchKey(text) {
3
+ return text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
4
+ }
5
+
6
+ /** True when every word of the query appears in some field, ignoring accents and case. */
7
+ export function matchesSearch(query, ...fields) {
8
+ const words = searchKey(query).split(" ").filter(Boolean);
9
+ if (!words.length) return true;
10
+ const haystack = searchKey(fields.filter(Boolean).join(" "));
11
+ return words.every(word => haystack.includes(word));
12
+ }