@businessdash/sdk 0.9.60

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +899 -0
  3. package/dist/adapters-revalidate.d.ts +44 -0
  4. package/dist/adapters-revalidate.d.ts.map +1 -0
  5. package/dist/adapters-revalidate.js +68 -0
  6. package/dist/adapters-revalidate.js.map +1 -0
  7. package/dist/alpha.d.ts +21 -0
  8. package/dist/alpha.d.ts.map +1 -0
  9. package/dist/alpha.js +21 -0
  10. package/dist/alpha.js.map +1 -0
  11. package/dist/analytics-core.d.ts +34 -0
  12. package/dist/analytics-core.d.ts.map +1 -0
  13. package/dist/analytics-core.js +138 -0
  14. package/dist/analytics-core.js.map +1 -0
  15. package/dist/analytics-server.d.ts +54 -0
  16. package/dist/analytics-server.d.ts.map +1 -0
  17. package/dist/analytics-server.js +78 -0
  18. package/dist/analytics-server.js.map +1 -0
  19. package/dist/attribution-logo.d.ts +2 -0
  20. package/dist/attribution-logo.d.ts.map +1 -0
  21. package/dist/attribution-logo.js +5 -0
  22. package/dist/attribution-logo.js.map +1 -0
  23. package/dist/auth-handler.d.ts +92 -0
  24. package/dist/auth-handler.d.ts.map +1 -0
  25. package/dist/auth-handler.js +277 -0
  26. package/dist/auth-handler.js.map +1 -0
  27. package/dist/biab-forms.css +484 -0
  28. package/dist/cli.d.ts +46 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +1824 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/client.d.ts +1681 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +2845 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/collections.d.ts +13 -0
  37. package/dist/collections.d.ts.map +1 -0
  38. package/dist/collections.js +13 -0
  39. package/dist/collections.js.map +1 -0
  40. package/dist/contracts.d.ts +14609 -0
  41. package/dist/contracts.d.ts.map +1 -0
  42. package/dist/contracts.js +2428 -0
  43. package/dist/contracts.js.map +1 -0
  44. package/dist/data-model-schema.d.ts +962 -0
  45. package/dist/data-model-schema.d.ts.map +1 -0
  46. package/dist/data-model-schema.js +2295 -0
  47. package/dist/data-model-schema.js.map +1 -0
  48. package/dist/distribution.d.ts +38 -0
  49. package/dist/distribution.d.ts.map +1 -0
  50. package/dist/distribution.js +61 -0
  51. package/dist/distribution.js.map +1 -0
  52. package/dist/element.d.ts +121 -0
  53. package/dist/element.d.ts.map +1 -0
  54. package/dist/element.js +276 -0
  55. package/dist/element.js.map +1 -0
  56. package/dist/enums.d.ts +25 -0
  57. package/dist/enums.d.ts.map +1 -0
  58. package/dist/enums.js +38 -0
  59. package/dist/enums.js.map +1 -0
  60. package/dist/errors.d.ts +195 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +188 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/forms-core/autocomplete.d.ts +37 -0
  65. package/dist/forms-core/autocomplete.d.ts.map +1 -0
  66. package/dist/forms-core/autocomplete.js +82 -0
  67. package/dist/forms-core/autocomplete.js.map +1 -0
  68. package/dist/forms-core/business-function.d.ts +61 -0
  69. package/dist/forms-core/business-function.d.ts.map +1 -0
  70. package/dist/forms-core/business-function.js +149 -0
  71. package/dist/forms-core/business-function.js.map +1 -0
  72. package/dist/forms-core/calculation.d.ts +72 -0
  73. package/dist/forms-core/calculation.d.ts.map +1 -0
  74. package/dist/forms-core/calculation.js +103 -0
  75. package/dist/forms-core/calculation.js.map +1 -0
  76. package/dist/forms-core/controller.d.ts +249 -0
  77. package/dist/forms-core/controller.d.ts.map +1 -0
  78. package/dist/forms-core/controller.js +590 -0
  79. package/dist/forms-core/controller.js.map +1 -0
  80. package/dist/forms-core/elements.d.ts +44 -0
  81. package/dist/forms-core/elements.d.ts.map +1 -0
  82. package/dist/forms-core/elements.js +68 -0
  83. package/dist/forms-core/elements.js.map +1 -0
  84. package/dist/forms-core/email-domains.d.ts +10 -0
  85. package/dist/forms-core/email-domains.d.ts.map +1 -0
  86. package/dist/forms-core/email-domains.js +69 -0
  87. package/dist/forms-core/email-domains.js.map +1 -0
  88. package/dist/forms-core/file-rules.d.ts +51 -0
  89. package/dist/forms-core/file-rules.d.ts.map +1 -0
  90. package/dist/forms-core/file-rules.js +105 -0
  91. package/dist/forms-core/file-rules.js.map +1 -0
  92. package/dist/forms-core/formula.d.ts +114 -0
  93. package/dist/forms-core/formula.d.ts.map +1 -0
  94. package/dist/forms-core/formula.js +1170 -0
  95. package/dist/forms-core/formula.js.map +1 -0
  96. package/dist/forms-core/index.d.ts +24 -0
  97. package/dist/forms-core/index.d.ts.map +1 -0
  98. package/dist/forms-core/index.js +28 -0
  99. package/dist/forms-core/index.js.map +1 -0
  100. package/dist/forms-core/units.d.ts +49 -0
  101. package/dist/forms-core/units.d.ts.map +1 -0
  102. package/dist/forms-core/units.js +255 -0
  103. package/dist/forms-core/units.js.map +1 -0
  104. package/dist/forms-core/us-states.d.ts +14 -0
  105. package/dist/forms-core/us-states.d.ts.map +1 -0
  106. package/dist/forms-core/us-states.js +66 -0
  107. package/dist/forms-core/us-states.js.map +1 -0
  108. package/dist/forms-core/validation.d.ts +266 -0
  109. package/dist/forms-core/validation.d.ts.map +1 -0
  110. package/dist/forms-core/validation.js +850 -0
  111. package/dist/forms-core/validation.js.map +1 -0
  112. package/dist/forms.d.ts +19 -0
  113. package/dist/forms.d.ts.map +1 -0
  114. package/dist/forms.js +29 -0
  115. package/dist/forms.js.map +1 -0
  116. package/dist/gallery-types.d.ts +32 -0
  117. package/dist/gallery-types.d.ts.map +1 -0
  118. package/dist/gallery-types.js +2 -0
  119. package/dist/gallery-types.js.map +1 -0
  120. package/dist/graphql.d.ts +97 -0
  121. package/dist/graphql.d.ts.map +1 -0
  122. package/dist/graphql.js +212 -0
  123. package/dist/graphql.js.map +1 -0
  124. package/dist/index.cjs +19448 -0
  125. package/dist/index.d.cts +19 -0
  126. package/dist/index.d.mts +19 -0
  127. package/dist/index.d.ts +19 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +20 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/marketing-schema.d.ts +216 -0
  132. package/dist/marketing-schema.d.ts.map +1 -0
  133. package/dist/marketing-schema.js +200 -0
  134. package/dist/marketing-schema.js.map +1 -0
  135. package/dist/mcp.d.ts +42 -0
  136. package/dist/mcp.d.ts.map +1 -0
  137. package/dist/mcp.js +87 -0
  138. package/dist/mcp.js.map +1 -0
  139. package/dist/native.d.ts +45 -0
  140. package/dist/native.d.ts.map +1 -0
  141. package/dist/native.js +43 -0
  142. package/dist/native.js.map +1 -0
  143. package/dist/next-revalidate.d.ts +43 -0
  144. package/dist/next-revalidate.d.ts.map +1 -0
  145. package/dist/next-revalidate.js +84 -0
  146. package/dist/next-revalidate.js.map +1 -0
  147. package/dist/parallel-pages.d.ts +102 -0
  148. package/dist/parallel-pages.d.ts.map +1 -0
  149. package/dist/parallel-pages.js +108 -0
  150. package/dist/parallel-pages.js.map +1 -0
  151. package/dist/payment-icons.d.ts +66 -0
  152. package/dist/payment-icons.d.ts.map +1 -0
  153. package/dist/payment-icons.js +160 -0
  154. package/dist/payment-icons.js.map +1 -0
  155. package/dist/proxy.d.ts +67 -0
  156. package/dist/proxy.d.ts.map +1 -0
  157. package/dist/proxy.js +97 -0
  158. package/dist/proxy.js.map +1 -0
  159. package/dist/react-analytics.d.ts +39 -0
  160. package/dist/react-analytics.d.ts.map +1 -0
  161. package/dist/react-analytics.js +59 -0
  162. package/dist/react-analytics.js.map +1 -0
  163. package/dist/react-attribution.d.ts +99 -0
  164. package/dist/react-attribution.d.ts.map +1 -0
  165. package/dist/react-attribution.js +52 -0
  166. package/dist/react-attribution.js.map +1 -0
  167. package/dist/react-marketing.d.ts +181 -0
  168. package/dist/react-marketing.d.ts.map +1 -0
  169. package/dist/react-marketing.js +215 -0
  170. package/dist/react-marketing.js.map +1 -0
  171. package/dist/react.d.ts +727 -0
  172. package/dist/react.d.ts.map +1 -0
  173. package/dist/react.js +2418 -0
  174. package/dist/react.js.map +1 -0
  175. package/dist/reserved-paths.d.ts +24 -0
  176. package/dist/reserved-paths.d.ts.map +1 -0
  177. package/dist/reserved-paths.js +60 -0
  178. package/dist/reserved-paths.js.map +1 -0
  179. package/dist/revalidate-verify.d.ts +30 -0
  180. package/dist/revalidate-verify.d.ts.map +1 -0
  181. package/dist/revalidate-verify.js +74 -0
  182. package/dist/revalidate-verify.js.map +1 -0
  183. package/dist/sdk.d.ts +678 -0
  184. package/dist/sdk.d.ts.map +1 -0
  185. package/dist/sdk.js +801 -0
  186. package/dist/sdk.js.map +1 -0
  187. package/dist/seo.d.ts +218 -0
  188. package/dist/seo.d.ts.map +1 -0
  189. package/dist/seo.js +309 -0
  190. package/dist/seo.js.map +1 -0
  191. package/dist/socials.d.ts +47 -0
  192. package/dist/socials.d.ts.map +1 -0
  193. package/dist/socials.js +172 -0
  194. package/dist/socials.js.map +1 -0
  195. package/dist/static.d.ts +14 -0
  196. package/dist/static.d.ts.map +1 -0
  197. package/dist/static.js +14 -0
  198. package/dist/static.js.map +1 -0
  199. package/dist/url-pattern.d.ts +47 -0
  200. package/dist/url-pattern.d.ts.map +1 -0
  201. package/dist/url-pattern.js +124 -0
  202. package/dist/url-pattern.js.map +1 -0
  203. package/dist/vanilla.d.ts +77 -0
  204. package/dist/vanilla.d.ts.map +1 -0
  205. package/dist/vanilla.js +903 -0
  206. package/dist/vanilla.js.map +1 -0
  207. package/openapi.json +13779 -0
  208. package/package.json +204 -0
  209. package/src/adapters-revalidate.ts +88 -0
  210. package/src/alpha.ts +89 -0
  211. package/src/analytics-core.ts +175 -0
  212. package/src/analytics-server.ts +99 -0
  213. package/src/angular/biab-form.component.ts +1443 -0
  214. package/src/angular/index.ts +26 -0
  215. package/src/attribution-logo.ts +5 -0
  216. package/src/auth-handler.ts +352 -0
  217. package/src/cli.ts +2517 -0
  218. package/src/client.ts +3578 -0
  219. package/src/collections.ts +101 -0
  220. package/src/contracts.ts +3390 -0
  221. package/src/data-model-schema.ts +4042 -0
  222. package/src/distribution.ts +73 -0
  223. package/src/element.ts +321 -0
  224. package/src/enums.ts +65 -0
  225. package/src/errors.ts +338 -0
  226. package/src/forms-core/autocomplete.ts +97 -0
  227. package/src/forms-core/business-function.ts +209 -0
  228. package/src/forms-core/calculation.ts +167 -0
  229. package/src/forms-core/controller.ts +922 -0
  230. package/src/forms-core/elements.ts +86 -0
  231. package/src/forms-core/email-domains.ts +68 -0
  232. package/src/forms-core/file-rules.ts +123 -0
  233. package/src/forms-core/formula.ts +1339 -0
  234. package/src/forms-core/index.ts +122 -0
  235. package/src/forms-core/units.ts +308 -0
  236. package/src/forms-core/us-states.ts +68 -0
  237. package/src/forms-core/validation.ts +1037 -0
  238. package/src/forms.ts +63 -0
  239. package/src/gallery-types.ts +31 -0
  240. package/src/graphql.ts +295 -0
  241. package/src/index.ts +505 -0
  242. package/src/marketing-schema.ts +395 -0
  243. package/src/mcp.ts +108 -0
  244. package/src/native.ts +73 -0
  245. package/src/next-revalidate.ts +124 -0
  246. package/src/parallel-pages.ts +192 -0
  247. package/src/payment-icons.ts +205 -0
  248. package/src/proxy.ts +173 -0
  249. package/src/qwik.tsx +1234 -0
  250. package/src/react-analytics.tsx +76 -0
  251. package/src/react-attribution.tsx +192 -0
  252. package/src/react-marketing.tsx +338 -0
  253. package/src/react.tsx +5495 -0
  254. package/src/reserved-paths.ts +65 -0
  255. package/src/revalidate-verify.ts +98 -0
  256. package/src/sdk.ts +1671 -0
  257. package/src/seo.ts +488 -0
  258. package/src/socials.ts +230 -0
  259. package/src/solid.tsx +1466 -0
  260. package/src/static.ts +21 -0
  261. package/src/styles/biab-forms.css +484 -0
  262. package/src/svelte/BiabForm.svelte +1069 -0
  263. package/src/svelte/context.ts +57 -0
  264. package/src/svelte/index.ts +79 -0
  265. package/src/url-pattern.ts +153 -0
  266. package/src/vanilla.ts +1290 -0
  267. package/src/vue/BiabForm.vue +1538 -0
  268. package/src/vue/index.ts +119 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Small, framework-free helpers the bindings share when rendering elements.
3
+ *
4
+ * Anything here would otherwise be copy-pasted into React, Svelte, Vue, Angular,
5
+ * Solid, Qwik and vanilla — and then drift. The dashboard renderer keeps its own
6
+ * copy of the threshold only because it can't import from the SDK; the values
7
+ * are deliberately identical.
8
+ */
9
+
10
+ /**
11
+ * Past this many options a dropdown grows a search affordance on its own.
12
+ * Scrolling a long list to find one entry is the worse default, and an author
13
+ * shouldn't have to notice their list got long. Mirrors `SEARCHABLE_THRESHOLD`
14
+ * in the dashboard builder's `constants.tsx`.
15
+ */
16
+ export const SEARCHABLE_THRESHOLD = 10;
17
+
18
+ /** Should this dropdown offer search? Either the author asked for it (the
19
+ * ComboBox card sets `display: "combobox"`) or the list is long enough. */
20
+ export function shouldSearchOptions(
21
+ optionCount: number,
22
+ display?: string,
23
+ ): boolean {
24
+ return display === "combobox" || optionCount > SEARCHABLE_THRESHOLD;
25
+ }
26
+
27
+ /**
28
+ * Where BIAB serves icon art.
29
+ *
30
+ * The `icon_select` element stores an icon NAME, and rendering it back needs the
31
+ * icon set. The SDK deliberately doesn't bundle one: it's ~1,600 icons, and
32
+ * three of the bindings can't import a React package anyway. Names resolve to
33
+ * `<img src="…/api/icons/<name>.svg">`, so the platform stays the single source
34
+ * of truth for what a name means.
35
+ *
36
+ * Override with `NEXT_PUBLIC_BIAB_ORIGIN` when pointing a consumer app at a
37
+ * non-production BIAB.
38
+ */
39
+ export const BIAB_ICON_BASE: string = (() => {
40
+ // Read through `globalThis` rather than the bare `process` identifier: the
41
+ // SDK is consumed by browser bundlers that have no `@types/node`, where
42
+ // naming `process` directly fails to compile (and can throw at runtime).
43
+ const env = (
44
+ globalThis as {
45
+ process?: { env?: Record<string, string | undefined> };
46
+ }
47
+ ).process?.env;
48
+ return env?.["NEXT_PUBLIC_BIAB_ORIGIN"] || "https://www.biab.app";
49
+ })();
50
+
51
+ /** SVG URL for one icon name. */
52
+ export function iconUrl(name: string): string {
53
+ return `${BIAB_ICON_BASE}/api/icons/${encodeURIComponent(name)}.svg`;
54
+ }
55
+
56
+ /** The lazily-fetched icon-name index (only needed when an author hasn't
57
+ * curated `iconConfig.allowedIcons`). */
58
+ export const ICON_INDEX_URL = `${BIAB_ICON_BASE}/api/icons`;
59
+
60
+ /** Narrow an icon list by a search query, capped so the grid never mounts
61
+ * thousands of nodes at once. */
62
+ export function filterIconNames(
63
+ names: readonly string[],
64
+ query: string,
65
+ limit = 120,
66
+ ): string[] {
67
+ const q = query.trim().toLowerCase();
68
+ return (q ? names.filter((n) => n.includes(q)) : names).slice(
69
+ 0,
70
+ limit,
71
+ ) as string[];
72
+ }
73
+
74
+ /** Fetch the icon-name index. Resolves to `[]` on any failure — a missing index
75
+ * leaves the picker's grid empty, which is recoverable; throwing here would
76
+ * take the whole form down. */
77
+ export async function fetchIconNames(): Promise<string[]> {
78
+ try {
79
+ const res = await fetch(ICON_INDEX_URL);
80
+ if (!res.ok) return [];
81
+ const data = (await res.json()) as { icons?: string[] };
82
+ return data.icons ?? [];
83
+ } catch {
84
+ return [];
85
+ }
86
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Free / consumer webmail detection for the email field's "business only" rule.
3
+ * Mirror of the app's `src/lib/onboarding-email-domain.ts` (the consumer-domain
4
+ * set + helpers) so the SDK's client validation agrees with the server.
5
+ */
6
+
7
+ const CONSUMER_EMAIL_DOMAINS = new Set([
8
+ "gmail.com",
9
+ "googlemail.com",
10
+ "yahoo.com",
11
+ "yahoo.co.uk",
12
+ "yahoo.fr",
13
+ "yahoo.de",
14
+ "hotmail.com",
15
+ "hotmail.co.uk",
16
+ "outlook.com",
17
+ "live.com",
18
+ "msn.com",
19
+ "icloud.com",
20
+ "me.com",
21
+ "mac.com",
22
+ "aol.com",
23
+ "proton.me",
24
+ "protonmail.com",
25
+ "pm.me",
26
+ "yandex.com",
27
+ "yandex.ru",
28
+ "mail.com",
29
+ "gmx.com",
30
+ "gmx.de",
31
+ "gmx.net",
32
+ "hey.com",
33
+ "fastmail.com",
34
+ "fastmail.fm",
35
+ "duck.com",
36
+ "tutanota.com",
37
+ "tutamail.com",
38
+ "qq.com",
39
+ "163.com",
40
+ "sbcglobal.net",
41
+ "att.net",
42
+ "verizon.net",
43
+ "comcast.net",
44
+ "btinternet.com",
45
+ "sky.com",
46
+ "orange.fr",
47
+ "wanadoo.fr",
48
+ "free.fr",
49
+ "laposte.net",
50
+ ]);
51
+
52
+ /** The lowercase host portion of an email, or null when malformed. */
53
+ export function getEmailDomain(email: string): string | null {
54
+ const at = email.lastIndexOf("@");
55
+ if (at < 0 || at === email.length - 1) return null;
56
+ return email
57
+ .slice(at + 1)
58
+ .trim()
59
+ .toLowerCase();
60
+ }
61
+
62
+ /** True for free/personal webmail hosts (so "business only" can reject them). */
63
+ export function isConsumerEmailDomain(host: string): boolean {
64
+ const h = host.trim().toLowerCase();
65
+ if (CONSUMER_EMAIL_DOMAINS.has(h)) return true;
66
+ if (h.endsWith(".gmail.com") || h.endsWith(".googlemail.com")) return true;
67
+ return false;
68
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * File-upload rules — the single source of truth for what a `file_upload` field
3
+ * accepts, shared by the builder picker, the live renderers, and all submission
4
+ * validators (so the client never disagrees with the server).
5
+ *
6
+ * Three concerns:
7
+ * - **iOS formats:** iPhones hand back HEIC photos + HEVC/`.mov` video. When a
8
+ * field allows images/video we transparently accept those too, so a visitor
9
+ * isn't blocked by their camera's default codec.
10
+ * - **Per-file rules:** allowed type + max size (MB), per the field config.
11
+ * - **Form-wide cap:** at most {@link FORM_WIDE_MAX_FILES} files across ALL
12
+ * file fields in one submission (anti-abuse; a field's own `maxFiles` still
13
+ * caps that field).
14
+ *
15
+ * Pure + isomorphic (no DOM, no DB). The SDK mirrors it.
16
+ */
17
+
18
+ /** HEIC/HEIF still-image formats (iPhone default photo codec). */
19
+ export const IOS_IMAGE_TYPES = ["image/heic", "image/heif"] as const;
20
+ /** iPhone video container (`.mov`, typically HEVC). */
21
+ export const IOS_VIDEO_TYPES = ["video/quicktime"] as const;
22
+
23
+ /** Max total files across every `file_upload` field in one submission. */
24
+ export const FORM_WIDE_MAX_FILES = 5;
25
+
26
+ export interface FileRule {
27
+ /** Allowed MIME types / wildcards (e.g. `image/*`) / extensions (`.pdf`). */
28
+ allowedTypes?: string[] | undefined;
29
+ /** Per-field file count cap. */
30
+ maxFiles?: number | undefined;
31
+ /** Per-file size cap in MB. */
32
+ maxSize?: number | undefined;
33
+ }
34
+
35
+ /** A minimal file descriptor — works for a real `File` or a stored value. */
36
+ export interface FileLike {
37
+ name: string;
38
+ type?: string | undefined;
39
+ size?: number | undefined;
40
+ }
41
+
42
+ function ext(name: string): string {
43
+ const dot = name.lastIndexOf(".");
44
+ return dot >= 0 ? name.slice(dot).toLowerCase() : "";
45
+ }
46
+
47
+ /** Does `image/*` / `video/*` appear in the allow-list (so iOS codecs apply)? */
48
+ function allowsImages(allowed: string[]): boolean {
49
+ return allowed.some(
50
+ (a) => a === "image/*" || (a.startsWith("image/") && !a.includes("heic")),
51
+ );
52
+ }
53
+ function allowsVideo(allowed: string[]): boolean {
54
+ return allowed.some((a) => a === "video/*" || a.startsWith("video/"));
55
+ }
56
+
57
+ /**
58
+ * Expand a field's `allowedTypes` to also accept iOS HEIC/HEVC when the field
59
+ * already allows images/video, then format the `<input accept>` attribute.
60
+ * Returns undefined (= accept anything) when no allow-list is set.
61
+ */
62
+ export function buildAcceptAttr(allowed?: string[]): string | undefined {
63
+ if (!allowed || allowed.length === 0) return undefined;
64
+ return expandAllowedTypes(allowed).join(",");
65
+ }
66
+
67
+ /** The allow-list plus the iOS formats implied by it (deduped, order-stable). */
68
+ export function expandAllowedTypes(allowed: string[]): string[] {
69
+ const out = [...allowed];
70
+ const add = (t: string) => {
71
+ if (!out.includes(t)) out.push(t);
72
+ };
73
+ if (allowsImages(allowed)) {
74
+ for (const t of IOS_IMAGE_TYPES) add(t);
75
+ add(".heic");
76
+ add(".heif");
77
+ }
78
+ if (allowsVideo(allowed)) {
79
+ for (const t of IOS_VIDEO_TYPES) add(t);
80
+ add(".mov");
81
+ }
82
+ return out;
83
+ }
84
+
85
+ /** Is `file` an accepted type for `allowed`? Empty/absent allow-list = anything.
86
+ * Handles exact MIME, `type/*` wildcards, and `.ext` entries; iOS HEIC/HEVC are
87
+ * honored whenever images/video are allowed. */
88
+ export function isFileTypeAllowed(file: FileLike, allowed?: string[]): boolean {
89
+ if (!allowed || allowed.length === 0) return true;
90
+ const list = expandAllowedTypes(allowed);
91
+ const type = (file.type ?? "").toLowerCase();
92
+ const fileExt = ext(file.name);
93
+ for (const raw of list) {
94
+ const a = raw.toLowerCase().trim();
95
+ if (!a) continue;
96
+ if (a.startsWith(".")) {
97
+ if (fileExt === a) return true;
98
+ continue;
99
+ }
100
+ if (a.endsWith("/*")) {
101
+ if (type.startsWith(`${a.slice(0, -1)}`)) return true;
102
+ continue;
103
+ }
104
+ if (type && type === a) return true;
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /** Per-file validation (type + size). Returns an error string or null. */
110
+ export function validateFile(file: FileLike, rule: FileRule): string | null {
111
+ if (!isFileTypeAllowed(file, rule.allowedTypes)) {
112
+ return `File type not allowed: ${file.type || file.name}`;
113
+ }
114
+ if (
115
+ typeof rule.maxSize === "number" &&
116
+ rule.maxSize > 0 &&
117
+ typeof file.size === "number" &&
118
+ file.size > rule.maxSize * 1024 * 1024
119
+ ) {
120
+ return `"${file.name}" exceeds the ${rule.maxSize} MB limit.`;
121
+ }
122
+ return null;
123
+ }