alepha 0.20.1 → 0.20.2

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 (232) hide show
  1. package/dist/api/files/index.js +2 -1
  2. package/dist/api/files/index.js.map +1 -1
  3. package/dist/api/jobs/index.browser.js +64 -148
  4. package/dist/api/jobs/index.browser.js.map +1 -1
  5. package/dist/api/jobs/index.d.ts +371 -573
  6. package/dist/api/jobs/index.d.ts.map +1 -1
  7. package/dist/api/jobs/index.js +605 -1012
  8. package/dist/api/jobs/index.js.map +1 -1
  9. package/dist/api/notifications/index.d.ts +78 -17
  10. package/dist/api/notifications/index.d.ts.map +1 -1
  11. package/dist/api/notifications/index.js +90 -23
  12. package/dist/api/notifications/index.js.map +1 -1
  13. package/dist/api/payments/index.d.ts +2 -1
  14. package/dist/api/payments/index.d.ts.map +1 -1
  15. package/dist/api/payments/index.js +4 -2
  16. package/dist/api/payments/index.js.map +1 -1
  17. package/dist/api/users/index.d.ts +34 -31
  18. package/dist/api/users/index.d.ts.map +1 -1
  19. package/dist/api/users/index.js +13 -7
  20. package/dist/api/users/index.js.map +1 -1
  21. package/dist/api/verifications/index.js +2 -1
  22. package/dist/api/verifications/index.js.map +1 -1
  23. package/dist/cli/core/index.d.ts +8 -34
  24. package/dist/cli/core/index.d.ts.map +1 -1
  25. package/dist/cli/core/index.js +43 -232
  26. package/dist/cli/core/index.js.map +1 -1
  27. package/dist/cli/platform/index.d.ts +36 -11
  28. package/dist/cli/platform/index.d.ts.map +1 -1
  29. package/dist/cli/platform/index.js +93 -27
  30. package/dist/cli/platform/index.js.map +1 -1
  31. package/dist/command/index.d.ts +1 -1
  32. package/dist/core/index.browser.js +6 -0
  33. package/dist/core/index.browser.js.map +1 -1
  34. package/dist/core/index.d.ts +6 -0
  35. package/dist/core/index.d.ts.map +1 -1
  36. package/dist/core/index.js +6 -0
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/index.native.js +6 -0
  39. package/dist/core/index.native.js.map +1 -1
  40. package/dist/core/index.workerd.js +6 -0
  41. package/dist/core/index.workerd.js.map +1 -1
  42. package/dist/react/form/index.d.ts +60 -1
  43. package/dist/react/form/index.d.ts.map +1 -1
  44. package/dist/react/form/index.js +86 -1
  45. package/dist/react/form/index.js.map +1 -1
  46. package/dist/react/head/index.browser.js +16 -1
  47. package/dist/react/head/index.browser.js.map +1 -1
  48. package/dist/react/head/index.d.ts +6 -0
  49. package/dist/react/head/index.d.ts.map +1 -1
  50. package/dist/react/head/index.js +16 -1
  51. package/dist/react/head/index.js.map +1 -1
  52. package/dist/react/router/index.browser.js +0 -10
  53. package/dist/react/router/index.browser.js.map +1 -1
  54. package/dist/react/router/index.d.ts +35 -12
  55. package/dist/react/router/index.d.ts.map +1 -1
  56. package/dist/react/router/index.js +0 -10
  57. package/dist/react/router/index.js.map +1 -1
  58. package/dist/react/ui/index.d.ts +124 -0
  59. package/dist/react/ui/index.d.ts.map +1 -0
  60. package/dist/react/ui/index.js +206 -0
  61. package/dist/react/ui/index.js.map +1 -0
  62. package/dist/router/index.d.ts +13 -13
  63. package/dist/router/index.d.ts.map +1 -1
  64. package/dist/router/index.js +45 -32
  65. package/dist/router/index.js.map +1 -1
  66. package/dist/system/index.d.ts.map +1 -1
  67. package/dist/system/index.js +1 -0
  68. package/dist/system/index.js.map +1 -1
  69. package/dist/topic/core/index.js +1 -1
  70. package/dist/topic/core/index.js.map +1 -1
  71. package/package.json +6 -23
  72. package/src/api/files/jobs/FileJobs.ts +2 -1
  73. package/src/api/jobs/__tests__/$job.spec.ts +316 -2867
  74. package/src/api/jobs/controllers/AdminJobController.ts +29 -138
  75. package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
  76. package/src/api/jobs/index.browser.ts +5 -7
  77. package/src/api/jobs/index.ts +23 -51
  78. package/src/api/jobs/primitives/$job.ts +66 -58
  79. package/src/api/jobs/providers/JobProvider.ts +561 -566
  80. package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
  81. package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
  82. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
  83. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
  84. package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
  85. package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
  86. package/src/api/jobs/services/JobService.ts +90 -483
  87. package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
  88. package/src/api/notifications/index.ts +7 -4
  89. package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
  90. package/src/api/payments/services/PaymentService.ts +4 -2
  91. package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
  92. package/src/api/users/audits/UserAudits.ts +3 -1
  93. package/src/api/users/buckets/UserBuckets.ts +2 -1
  94. package/src/api/users/index.ts +1 -4
  95. package/src/api/users/jobs/UserJobs.ts +5 -4
  96. package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
  97. package/src/cli/core/__tests__/init.spec.ts +1 -1
  98. package/src/cli/core/commands/init.ts +0 -12
  99. package/src/cli/core/services/PackageManagerUtils.ts +2 -9
  100. package/src/cli/core/services/ProjectScaffolder.ts +17 -65
  101. package/src/cli/core/templates/agentMd.ts +2 -8
  102. package/src/cli/core/templates/apiIndexTs.ts +4 -18
  103. package/src/cli/core/templates/mainCss.ts +1 -36
  104. package/src/cli/core/templates/vitestConfigTs.ts +17 -0
  105. package/src/cli/core/templates/webAppRouterTs.ts +2 -85
  106. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
  107. package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
  108. package/src/cli/platform/atoms/platformOptions.ts +9 -0
  109. package/src/cli/platform/schemas/cloudflare.ts +3 -2
  110. package/src/cli/platform/services/CloudflareApi.ts +164 -25
  111. package/src/cli/platform/services/WranglerApi.ts +0 -17
  112. package/src/core/Alepha.ts +9 -0
  113. package/src/react/form/index.ts +2 -0
  114. package/src/react/form/services/parseField.ts +163 -0
  115. package/src/react/form/services/prettyName.ts +19 -0
  116. package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
  117. package/src/react/router/primitives/$page.ts +35 -12
  118. package/src/react/ui/atoms/uiAtom.ts +28 -0
  119. package/src/react/ui/components/ColorScheme.tsx +36 -0
  120. package/src/react/ui/hooks/useColorMode.ts +49 -0
  121. package/src/react/ui/hooks/useSidebarState.ts +26 -0
  122. package/src/react/ui/hooks/useTheme.ts +22 -0
  123. package/src/react/ui/index.ts +35 -0
  124. package/src/react/ui/services/UiPersistence.ts +41 -0
  125. package/src/router/TemplatedPathParser.ts +50 -51
  126. package/src/router/__tests__/RouterProvider.spec.ts +62 -0
  127. package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
  128. package/src/router/providers/RouterProvider.ts +10 -5
  129. package/src/system/providers/NodeShellProvider.ts +1 -0
  130. package/src/topic/core/providers/TopicProvider.ts +1 -1
  131. package/dist/api/invitations/index.d.ts +0 -790
  132. package/dist/api/invitations/index.d.ts.map +0 -1
  133. package/dist/api/invitations/index.js +0 -662
  134. package/dist/api/invitations/index.js.map +0 -1
  135. package/dist/api/issues/index.d.ts +0 -810
  136. package/dist/api/issues/index.d.ts.map +0 -1
  137. package/dist/api/issues/index.js +0 -444
  138. package/dist/api/issues/index.js.map +0 -1
  139. package/dist/api/subscriptions/index.d.ts +0 -1692
  140. package/dist/api/subscriptions/index.d.ts.map +0 -1
  141. package/dist/api/subscriptions/index.js +0 -1867
  142. package/dist/api/subscriptions/index.js.map +0 -1
  143. package/dist/api/workflows/index.browser.js +0 -246
  144. package/dist/api/workflows/index.browser.js.map +0 -1
  145. package/dist/api/workflows/index.d.ts +0 -1618
  146. package/dist/api/workflows/index.d.ts.map +0 -1
  147. package/dist/api/workflows/index.js +0 -1495
  148. package/dist/api/workflows/index.js.map +0 -1
  149. package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
  150. package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
  151. package/src/api/invitations/controllers/InvitationController.ts +0 -84
  152. package/src/api/invitations/entities/invitations.ts +0 -33
  153. package/src/api/invitations/index.ts +0 -58
  154. package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
  155. package/src/api/invitations/providers/InvitationProvider.ts +0 -45
  156. package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
  157. package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
  158. package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
  159. package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
  160. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
  161. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
  162. package/src/api/invitations/services/InvitationService.ts +0 -556
  163. package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
  164. package/src/api/issues/controllers/AdminIssueController.ts +0 -149
  165. package/src/api/issues/controllers/IssueController.ts +0 -44
  166. package/src/api/issues/entities/issues.ts +0 -49
  167. package/src/api/issues/index.ts +0 -50
  168. package/src/api/issues/schemas/createIssueSchema.ts +0 -13
  169. package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
  170. package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
  171. package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
  172. package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
  173. package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
  174. package/src/api/issues/services/IssueService.ts +0 -264
  175. package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
  176. package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
  177. package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
  178. package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
  179. package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
  180. package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
  181. package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
  182. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
  183. package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
  184. package/src/api/jobs/services/JobService-tests.ts +0 -157
  185. package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
  186. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
  187. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
  188. package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
  189. package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
  190. package/src/api/subscriptions/entities/subscriptions.ts +0 -68
  191. package/src/api/subscriptions/index.ts +0 -133
  192. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
  193. package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
  194. package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
  195. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
  196. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
  197. package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
  198. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
  199. package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
  200. package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
  201. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
  202. package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
  203. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
  204. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
  205. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
  206. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
  207. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
  208. package/src/api/subscriptions/services/BillingService.ts +0 -437
  209. package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
  210. package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
  211. package/src/api/subscriptions/services/UsageService.ts +0 -118
  212. package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
  213. package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
  214. package/src/api/workflows/entities/workflowExecutions.ts +0 -74
  215. package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
  216. package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
  217. package/src/api/workflows/index.browser.ts +0 -22
  218. package/src/api/workflows/index.ts +0 -115
  219. package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
  220. package/src/api/workflows/primitives/$workflow.ts +0 -202
  221. package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
  222. package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
  223. package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
  224. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
  225. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
  226. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
  227. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
  228. package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
  229. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
  230. package/src/api/workflows/services/WorkflowService.ts +0 -382
  231. package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
  232. package/src/cli/core/templates/webAdminDashboardTsx.ts +0 -17
@@ -198,6 +198,65 @@ declare const useFormState: <T extends TObject, Keys extends keyof UseFormStateR
198
198
  */
199
199
  declare const useFormValues: <T extends TObject>(form: FormModel<T>) => Record<string, any>;
200
200
  //#endregion
201
+ //#region ../../src/react/form/services/parseField.d.ts
202
+ /**
203
+ * Semantic icon hint derived from schema metadata. UI layers map this
204
+ * to their own icon set — this module is headless and ships no JSX.
205
+ */
206
+ type IconHint = "email" | "password" | "phone" | "url" | "number" | "calendar" | "clock" | "list" | "text" | "user" | "file" | "switch";
207
+ interface FieldConstraints {
208
+ minLength?: number;
209
+ maxLength?: number;
210
+ minimum?: number;
211
+ maximum?: number;
212
+ pattern?: string;
213
+ }
214
+ interface FieldMeta {
215
+ id?: string;
216
+ label: string;
217
+ description?: string;
218
+ error?: string;
219
+ required: boolean;
220
+ type?: string;
221
+ format?: string;
222
+ isEnum: boolean;
223
+ isArray: boolean;
224
+ isObject: boolean;
225
+ isArrayOfObjects: boolean;
226
+ enum?: readonly unknown[];
227
+ iconHint?: IconHint;
228
+ constraints: FieldConstraints;
229
+ testId?: string;
230
+ schema: TSchema;
231
+ }
232
+ interface ParseFieldOptions {
233
+ label?: string;
234
+ description?: string;
235
+ error?: Error;
236
+ }
237
+ /**
238
+ * Derives a {@link FieldMeta} from an `InputField` (from `useForm`) plus
239
+ * optional overrides. Pure — no React, no JSX, no UI library coupling.
240
+ *
241
+ * UI components consume this metadata to render labels, descriptions,
242
+ * error messages, icons, and validation constraints.
243
+ */
244
+ declare const parseField: (input: BaseInputField, options?: ParseFieldOptions) => FieldMeta;
245
+ //#endregion
246
+ //#region ../../src/react/form/services/prettyName.d.ts
247
+ /**
248
+ * Converts a path or identifier string into a pretty display name.
249
+ * For paths like "/contacts/0/name", extracts just the field name "Name".
250
+ * Handles camelCase and snake_case conversion to Title Case.
251
+ *
252
+ * @example
253
+ * prettyName("/userName") // "User Name"
254
+ * prettyName("/contacts/0/email") // "Email"
255
+ * prettyName("/address/streetName") // "Street Name"
256
+ * prettyName("first_name") // "First Name"
257
+ */
258
+ declare const prettyName: (name: string) => string;
259
+ //#endregion
201
260
  //#region ../../src/react/form/index.d.ts
202
261
  declare module "alepha" {
203
262
  interface Hooks {
@@ -239,5 +298,5 @@ declare module "alepha" {
239
298
  */
240
299
  declare const AlephaReactForm: _$alepha.Service<_$alepha.Module>;
241
300
  //#endregion
242
- export { AlephaReactForm, ArrayInputField, BaseInputField, FormCtrlOptions, FormEventLike, FormModel, FormState, FormValidationError, InputField, InputHTMLAttributesLike, ObjectInputField, SchemaToInput, UseFormStateReturn, useFieldValue, useForm, useFormState, useFormValues };
301
+ export { AlephaReactForm, ArrayInputField, BaseInputField, FieldConstraints, FieldMeta, FormCtrlOptions, FormEventLike, FormModel, FormState, FormValidationError, IconHint, InputField, InputHTMLAttributesLike, ObjectInputField, ParseFieldOptions, SchemaToInput, UseFormStateReturn, parseField, prettyName, useFieldValue, useForm, useFormState, useFormValues };
243
302
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/form/services/FormModel.ts","../../../src/react/form/components/FormState.tsx","../../../src/react/form/errors/FormValidationError.ts","../../../src/react/form/hooks/useFieldValue.ts","../../../src/react/form/hooks/useForm.ts","../../../src/react/form/hooks/useFormState.ts","../../../src/react/form/hooks/useFormValues.ts","../../../src/react/form/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/form/services/FormModel.ts","../../../src/react/form/components/FormState.tsx","../../../src/react/form/errors/FormValidationError.ts","../../../src/react/form/hooks/useFieldValue.ts","../../../src/react/form/hooks/useForm.ts","../../../src/react/form/hooks/useFormState.ts","../../../src/react/form/hooks/useFormValues.ts","../../../src/react/form/services/parseField.ts","../../../src/react/form/services/prettyName.ts","../../../src/react/form/index.ts"],"mappings":"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"}
@@ -457,6 +457,91 @@ const useFormValues = (form) => {
457
457
  return values;
458
458
  };
459
459
  //#endregion
460
+ //#region ../../src/react/form/services/prettyName.ts
461
+ /**
462
+ * Converts a path or identifier string into a pretty display name.
463
+ * For paths like "/contacts/0/name", extracts just the field name "Name".
464
+ * Handles camelCase and snake_case conversion to Title Case.
465
+ *
466
+ * @example
467
+ * prettyName("/userName") // "User Name"
468
+ * prettyName("/contacts/0/email") // "Email"
469
+ * prettyName("/address/streetName") // "Street Name"
470
+ * prettyName("first_name") // "First Name"
471
+ */
472
+ const prettyName = (name) => {
473
+ const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
474
+ return (segments[segments.length - 1] || name.replaceAll("/", "")).replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
475
+ };
476
+ //#endregion
477
+ //#region ../../src/react/form/services/parseField.ts
478
+ /**
479
+ * Derives a {@link FieldMeta} from an `InputField` (from `useForm`) plus
480
+ * optional overrides. Pure — no React, no JSX, no UI library coupling.
481
+ *
482
+ * UI components consume this metadata to render labels, descriptions,
483
+ * error messages, icons, and validation constraints.
484
+ */
485
+ const parseField = (input, options = {}) => {
486
+ const schema = input.schema;
487
+ const label = options.label ?? (typeof schema.title === "string" ? schema.title : void 0) ?? prettyName(input.path);
488
+ const description = options.description ?? (typeof schema.description === "string" ? schema.description : void 0);
489
+ const error = options.error instanceof TypeBoxError ? options.error.value?.message : void 0;
490
+ const type = schema.type;
491
+ const format = typeof schema.format === "string" ? schema.format : void 0;
492
+ const isEnum = Array.isArray(schema.enum);
493
+ const isArray = type === "array";
494
+ const isObject = type === "object" && Boolean(schema.properties);
495
+ const isArrayOfObjects = isArray && Boolean(schema.items && schema.items.properties);
496
+ const name = input.props.name;
497
+ const iconHint = inferIconHint({
498
+ type,
499
+ format,
500
+ name,
501
+ isEnum,
502
+ isArray
503
+ });
504
+ const constraints = {};
505
+ if (typeof schema.minLength === "number") constraints.minLength = schema.minLength;
506
+ if (typeof schema.maxLength === "number") constraints.maxLength = schema.maxLength;
507
+ if (typeof schema.minimum === "number") constraints.minimum = schema.minimum;
508
+ if (typeof schema.maximum === "number") constraints.maximum = schema.maximum;
509
+ if (typeof schema.pattern === "string") constraints.pattern = schema.pattern;
510
+ return {
511
+ id: input.props.id,
512
+ label,
513
+ description,
514
+ error,
515
+ required: input.required,
516
+ type,
517
+ format,
518
+ isEnum,
519
+ isArray,
520
+ isObject,
521
+ isArrayOfObjects,
522
+ enum: schema.enum,
523
+ iconHint,
524
+ constraints,
525
+ testId: input.props["data-testid"],
526
+ schema: input.schema
527
+ };
528
+ };
529
+ const inferIconHint = (params) => {
530
+ const { type, format, name, isEnum, isArray } = params;
531
+ if (format === "email") return "email";
532
+ if (format === "url" || format === "uri") return "url";
533
+ if (format === "tel" || format === "phone") return "phone";
534
+ if (format === "date" || format === "date-time") return "calendar";
535
+ if (format === "time") return "clock";
536
+ if (name?.toLowerCase().includes("password")) return "password";
537
+ if (name?.toLowerCase().includes("email")) return "email";
538
+ if (name?.toLowerCase().includes("phone")) return "phone";
539
+ if (type === "boolean") return "switch";
540
+ if (type === "number" || type === "integer") return "number";
541
+ if (isEnum || isArray) return "list";
542
+ if (type === "string") return "text";
543
+ };
544
+ //#endregion
460
545
  //#region ../../src/react/form/index.ts
461
546
  /**
462
547
  * Type-safe forms with validation.
@@ -472,6 +557,6 @@ const useFormValues = (form) => {
472
557
  */
473
558
  const AlephaReactForm = $module({ name: "alepha.react.form" });
474
559
  //#endregion
475
- export { AlephaReactForm, FormModel, FormState, FormValidationError, useFieldValue, useForm, useFormState, useFormValues };
560
+ export { AlephaReactForm, FormModel, FormState, FormValidationError, parseField, prettyName, useFieldValue, useForm, useFormState, useFormValues };
476
561
 
477
562
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/form/hooks/useFormState.ts","../../../src/react/form/components/FormState.tsx","../../../src/react/form/errors/FormValidationError.ts","../../../src/react/form/hooks/useFieldValue.ts","../../../src/react/form/services/FormModel.ts","../../../src/react/form/hooks/useForm.ts","../../../src/react/form/hooks/useFormValues.ts","../../../src/react/form/index.ts"],"sourcesContent":["import { type TObject, TypeBoxError } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nexport interface UseFormStateReturn {\n loading: boolean;\n dirty: boolean;\n values?: Record<string, any>;\n error?: Error;\n}\n\nexport const useFormState = <\n T extends TObject,\n Keys extends keyof UseFormStateReturn,\n>(\n target: FormModel<T> | { form: FormModel<T>; path: string },\n _events: Keys[] = [\"loading\", \"dirty\", \"error\"] as Keys[],\n): Pick<UseFormStateReturn, Keys> => {\n const alepha = useAlepha();\n const events = _events as string[];\n\n const [dirty, setDirty] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [values, setValues] = useState<Record<string, any> | undefined>(\n undefined,\n );\n\n const form = \"form\" in target ? target.form : target;\n const path = \"path\" in target ? target.path : undefined;\n\n const hasValues = events.includes(\"values\");\n const hasErrors = events.includes(\"error\");\n const hasDirty = events.includes(\"dirty\");\n const hasLoading = events.includes(\"loading\");\n\n useEffect(() => {\n const listeners: Function[] = [];\n\n if (hasErrors || hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:change\", (event) => {\n if (event.id === form.id) {\n if (!path || event.path === path) {\n if (hasDirty) {\n setDirty(true);\n }\n if (hasErrors) {\n setError(undefined);\n }\n }\n if (hasValues) {\n setValues(form.currentValues);\n }\n }\n }),\n );\n }\n\n if (hasLoading) {\n listeners.push(\n alepha.events.on(\"form:submit:begin\", (event) => {\n if (event.id === form.id) {\n setLoading(true);\n }\n }),\n alepha.events.on(\"form:submit:end\", (event) => {\n if (event.id === form.id) {\n setLoading(false);\n }\n }),\n );\n }\n\n if (hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:submit:success\", (event) => {\n if (event.id === form.id) {\n if (hasValues) {\n setValues(event.values);\n }\n if (hasDirty) {\n setDirty(false);\n }\n }\n }),\n );\n }\n\n if (hasDirty) {\n listeners.push(\n alepha.events.on(\"form:reset\", (event) => {\n if (event.id === form.id) {\n setDirty(false);\n }\n }),\n );\n }\n\n if (hasErrors) {\n listeners.push(\n alepha.events.on(\"form:submit:error\", (event) => {\n if (event.id === form.id) {\n if (\n !path ||\n (event.error instanceof TypeBoxError &&\n event.error.value.path === path)\n ) {\n setError(event.error);\n }\n }\n }),\n );\n }\n\n return () => {\n for (const unsub of listeners) {\n unsub();\n }\n };\n }, []);\n\n return {\n dirty,\n loading,\n error,\n values,\n } as Pick<UseFormStateReturn, Keys>;\n};\n","import type { TObject } from \"alepha\";\nimport type { ReactNode } from \"react\";\nimport { useFormState } from \"../hooks/useFormState.ts\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nconst FormState = <T extends TObject>(props: {\n form: FormModel<T>;\n children: (state: { loading: boolean; dirty: boolean }) => ReactNode;\n}) => {\n const formState = useFormState(props.form);\n return props.children({\n loading: formState.loading,\n dirty: formState.dirty,\n });\n};\n\nexport default FormState;\n","import { TypeBoxError } from \"alepha\";\n\nexport class FormValidationError extends TypeBoxError {\n readonly name = \"ValidationError\";\n\n constructor(options: {\n message: string;\n path: string;\n }) {\n super({\n message: options.message,\n instancePath: options.path,\n schemaPath: \"\",\n keyword: \"not\",\n params: {},\n });\n }\n}\n","import { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { BaseInputField } from \"../services/FormModel.ts\";\n\n/**\n * Hook to subscribe to a single form field's value.\n * Only re-renders when this specific field changes.\n *\n * @returns A tuple of [value, setValue] similar to useState.\n */\nexport const useFieldValue = (\n input: BaseInputField,\n): [any, (value: any) => void] => {\n const alepha = useAlepha();\n const [value, setValue] = useState(input?.initialValue);\n\n useEffect(() => {\n if (!input?.form || !alepha.isBrowser()) {\n return;\n }\n\n return alepha.events.on(\"form:change\", (ev) => {\n if (ev.id === input.form.id && ev.path === input.path) {\n setValue(ev.value);\n }\n });\n }, []);\n\n const setFieldValue = (newValue: any) => {\n input.set(newValue);\n };\n\n return [value, setFieldValue];\n};\n","import type { TArray } from \"alepha\";\nimport {\n $inject,\n Alepha,\n type Static,\n type TObject,\n type TSchema,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { InputHTMLAttributes } from \"react\";\n\n/**\n * FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.\n * It manages form state, handles input changes, and processes form submissions with validation.\n *\n * It means to be injected and used within React components to provide a structured way to create and manage forms.\n *\n * @see {@link useForm}\n */\nexport class FormModel<T extends TObject> {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly values: Record<string, any> = {};\n protected readonly initialValues: Record<string, any> = {};\n protected submitInProgress = false;\n\n public input: SchemaToInput<T>;\n\n public get submitting(): boolean {\n return this.submitInProgress;\n }\n\n constructor(\n public readonly id: string,\n public readonly options: FormCtrlOptions<T>,\n ) {\n this.options = options;\n\n // Initialize with schema defaults first, then override with initialValues\n const schemaDefaults = this.extractSchemaDefaults(options.schema);\n if (Object.keys(schemaDefaults).length > 0) {\n Object.assign(this.values, schemaDefaults);\n }\n\n if (options.initialValues) {\n const decoded = this.alepha.codec.decode(\n options.schema,\n options.initialValues,\n ) as Record<string, any>;\n Object.assign(this.values, decoded);\n }\n\n this.initialValues = { ...this.values };\n\n this.input = this.createProxyFromSchema(options, options.schema, {\n store: this.values,\n parent: \"\",\n });\n }\n\n /**\n * Extract default values from a TypeBox schema.\n * Recursively handles nested objects.\n */\n protected extractSchemaDefaults(\n schema: TObject,\n prefix: string = \"\",\n ): Record<string, any> {\n const defaults: Record<string, any> = {};\n\n if (!schema.properties) {\n return defaults;\n }\n\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (\"default\" in propSchema && propSchema.default !== undefined) {\n defaults[fullKey] = propSchema.default;\n } else if (\n propSchema &&\n \"type\" in propSchema &&\n propSchema.type === \"object\" &&\n \"properties\" in propSchema\n ) {\n // Recursively extract defaults from nested objects\n const nestedDefaults = this.extractSchemaDefaults(\n propSchema as TObject,\n fullKey,\n );\n Object.assign(defaults, nestedDefaults);\n }\n }\n\n return defaults;\n }\n\n public get currentValues(): Record<string, any> {\n return this.restructureValues(this.values);\n }\n\n public get props() {\n return {\n id: this.id,\n noValidate: true,\n onSubmit: (ev?: FormEventLike) => {\n ev?.preventDefault?.();\n this.submit();\n },\n onReset: (event: FormEventLike) => this.reset(event),\n };\n }\n\n public readonly setInitialValues = (values: Record<string, any>) => {\n const decoded = this.alepha.codec.decode(\n this.options.schema,\n values,\n ) as Record<string, any>;\n\n for (const key in this.initialValues) {\n delete (this.initialValues as Record<string, any>)[key];\n }\n Object.assign(this.initialValues, decoded);\n\n for (const key in this.values) {\n delete this.values[key];\n }\n Object.assign(this.values, { ...this.initialValues });\n\n for (const [key, value] of Object.entries(this.values)) {\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path, value },\n { catch: true },\n );\n }\n };\n\n public readonly reset = (event?: FormEventLike) => {\n event?.preventDefault?.();\n for (const key in this.values) {\n delete this.values[key];\n }\n Object.assign(this.values, { ...this.initialValues });\n for (const [key, value] of Object.entries(this.values)) {\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path, value },\n { catch: true },\n );\n }\n this.alepha.events.emit(\"form:reset\", { id: this.id }, { catch: true });\n this.options.onReset?.();\n };\n\n public readonly submit = async () => {\n if (this.submitInProgress) {\n this.log.warn(\n \"Form submission already in progress, ignoring duplicate submit.\",\n );\n return;\n }\n\n // emit both action and form events\n await this.alepha.events.emit(\"react:action:begin\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:begin\", {\n id: this.id,\n });\n\n this.submitInProgress = true;\n\n const options = this.options;\n\n try {\n let values: Record<string, any> = this.restructureValues(this.values);\n\n if (t.schema.isSchema(options.schema)) {\n values = this.alepha.codec.decode(options.schema, values) as Record<\n string,\n any\n >;\n }\n\n await options.handler(values as any);\n\n await this.alepha.events.emit(\"react:action:success\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:success\", {\n id: this.id,\n values,\n });\n } catch (error) {\n this.log.error(\"Form submission error:\", error);\n\n options.onError?.(error as Error);\n\n await this.alepha.events.emit(\"react:action:error\", {\n type: \"form\",\n id: this.id,\n error: error as Error,\n });\n await this.alepha.events.emit(\"form:submit:error\", {\n error: error as Error,\n id: this.id,\n });\n } finally {\n this.submitInProgress = false;\n }\n\n await this.alepha.events.emit(\"react:action:end\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:end\", {\n id: this.id,\n });\n };\n\n /**\n * Restructures flat keys like \"address.city\" into nested objects like { address: { city: ... } }\n * Values are already typed from onChange, so no conversion is needed.\n */\n protected restructureValues(store: Record<string, any>): Record<string, any> {\n const values: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(store)) {\n if (key.includes(\".\")) {\n // nested object: restructure flat key to nested structure\n this.restructureNestedValue(values, key, value);\n } else {\n // value is already typed, just copy it\n values[key] = value;\n }\n }\n\n return values;\n }\n\n /**\n * Helper to restructure a flat key like \"address.city\" into nested object structure.\n * The value is already typed, so we just assign it to the nested path.\n */\n protected restructureNestedValue(\n values: Record<string, any>,\n key: string,\n value: any,\n ) {\n const pathSegments = key.split(\".\");\n const finalPropertyKey = pathSegments.pop();\n if (!finalPropertyKey) {\n return;\n }\n\n let currentObjectLevel = values;\n\n // traverse/create the nested structure\n for (const segment of pathSegments) {\n currentObjectLevel[segment] ??= {};\n currentObjectLevel = currentObjectLevel[segment];\n }\n\n // value is already typed from onChange, just assign it\n currentObjectLevel[finalPropertyKey] = value;\n }\n\n protected createProxyFromSchema<T extends TObject>(\n options: FormCtrlOptions<T>,\n schema: TSchema,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): SchemaToInput<T> {\n const parent = context.parent || \"\";\n return new Proxy<SchemaToInput<T>>({} as SchemaToInput<T>, {\n get: (_, prop: string) => {\n if (!options.schema || !t.schema.isObject(schema)) {\n return {};\n }\n\n if (prop in schema.properties) {\n // // it's a nested object, create another proxy\n // if (t.schema.isObject(schema.properties[prop])) {\n // return this.createProxyFromSchema(\n // options,\n // schema.properties[prop],\n // {\n // parent: parent ? `${parent}.${prop}` : prop,\n // store: context.store,\n // },\n // );\n // }\n\n return this.createInputFromSchema<T>(\n prop as keyof Static<T> & string,\n options,\n schema,\n schema.required?.includes(prop as string) || false,\n context,\n );\n }\n },\n });\n }\n\n protected createInputFromSchema<T extends TObject>(\n name: keyof Static<T> & string,\n options: FormCtrlOptions<T>,\n schema: TObject,\n required: boolean,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): BaseInputField {\n const parent = context.parent || \"\";\n const field = schema.properties?.[name];\n if (!field) {\n return {\n path: \"\",\n required,\n initialValue: undefined,\n props: {} as InputHTMLAttributes<unknown>,\n schema: schema,\n set: () => {},\n form: this,\n };\n }\n\n const isRequired = schema.required?.includes(name) ?? false;\n const key = parent ? `${parent}.${name}` : name;\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n\n const set = (value: any) => {\n const typedValue = this.getValueFromInput(value, field);\n context.store[key] = typedValue;\n if (options.onChange) {\n options.onChange(key, typedValue, context.store);\n }\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path: path, value: typedValue },\n { catch: true },\n );\n };\n\n const attr: InputHTMLAttributesLike = {\n name: key,\n };\n\n if (options.id) {\n attr.id = `${options.id}-${key}`;\n (attr as any)[\"data-testid\"] = attr.id;\n }\n\n if (t.schema.isString(field)) {\n if (field.maxLength != null) {\n attr.maxLength = Number(field.maxLength);\n }\n\n if (field.minLength != null) {\n attr.minLength = Number(field.minLength);\n }\n }\n\n if (isRequired) {\n attr.required = true;\n }\n\n if (\"description\" in field && typeof field.description === \"string\") {\n attr[\"aria-label\"] = field.description;\n }\n\n if (t.schema.isInteger(field) || t.schema.isNumber(field)) {\n attr.type = \"number\";\n } else if (name === \"password\") {\n attr.type = \"password\";\n } else if (name === \"email\") {\n attr.type = \"email\";\n } else if (name === \"url\") {\n attr.type = \"url\";\n } else if (t.schema.isString(field)) {\n if (field.format === \"binary\") {\n attr.type = \"file\";\n } else if (field.format === \"date\") {\n attr.type = \"date\";\n } else if (field.format === \"time\") {\n attr.type = \"time\";\n } else if (field.format === \"date-time\") {\n attr.type = \"datetime-local\";\n } else {\n attr.type = \"text\";\n }\n } else if (t.schema.isBoolean(field)) {\n attr.type = \"checkbox\";\n }\n\n if (options.onCreateField) {\n const customAttr = options.onCreateField(name, field);\n Object.assign(attr, customAttr);\n }\n\n // if type = object, add items: { [key: string]: InputField }\n if (t.schema.isObject(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n items: this.createProxyFromSchema(options, field, {\n parent: key,\n store: context.store,\n }),\n } as ObjectInputField<any>;\n }\n\n // if type = array, add items: InputField[]\n if (t.schema.isArray(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n items: [], // <- will be populated dynamically in the UI\n } as ArrayInputField<any>;\n }\n\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n };\n }\n\n /**\n * Convert an input value to the correct type based on the schema.\n * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)\n */\n protected getValueFromInput(input: any, schema: TSchema): any {\n if (input instanceof File) {\n // for file inputs, return the File object directly\n if (t.schema.isString(schema) && schema.format === \"binary\") {\n return input;\n }\n // for now, ignore other formats\n return null;\n }\n\n if (t.schema.isBoolean(schema)) {\n // Handle string representations from Select components (Yes/No dropdown)\n if (input === \"true\") return true;\n if (input === \"false\") return false;\n if (input === \"\" || input === null || input === undefined)\n return undefined;\n // Handle actual boolean values\n return !!input;\n }\n\n if (t.schema.isNumber(schema)) {\n const num = Number(input);\n return Number.isNaN(num) ? null : num;\n }\n\n if (t.schema.isString(schema)) {\n if (schema.format === \"date\") {\n return new Date(input).toISOString().slice(0, 10); // For date input\n }\n if (schema.format === \"time\") {\n return new Date(`1970-01-01T${input}`).toISOString().slice(11, 16); // For time input\n }\n if (schema.format === \"date-time\") {\n return new Date(input).toISOString(); // For datetime-local input\n }\n return String(input);\n }\n\n return input; // fallback for other types\n }\n}\n\nexport type SchemaToInput<T extends TObject> = {\n [K in keyof T[\"properties\"]]: InputField<T[\"properties\"][K]>;\n};\n\nexport interface FormEventLike {\n preventDefault?: () => void;\n stopPropagation?: () => void;\n}\n\nexport type InputField<T extends TSchema> = T extends TObject\n ? ObjectInputField<T>\n : T extends TArray<infer U>\n ? ArrayInputField<U>\n : BaseInputField;\n\nexport interface BaseInputField {\n path: string;\n required: boolean;\n initialValue: any;\n props: InputHTMLAttributesLike;\n schema: TSchema;\n set: (value: any) => void;\n form: FormModel<any>;\n items?: any;\n}\n\nexport interface ObjectInputField<T extends TObject> extends BaseInputField {\n items: SchemaToInput<T>;\n}\n\nexport interface ArrayInputField<T extends TSchema> extends BaseInputField {\n items: Array<InputField<T>>;\n}\n\nexport type InputHTMLAttributesLike = Pick<\n InputHTMLAttributes<unknown>,\n | \"id\"\n | \"name\"\n | \"type\"\n | \"value\"\n | \"required\"\n | \"maxLength\"\n | \"minLength\"\n | \"aria-label\"\n> & {\n value?: any;\n};\n\nexport type FormCtrlOptions<T extends TObject> = {\n /**\n * The schema defining the structure and validation rules for the form.\n * This should be a TypeBox schema object.\n */\n schema: T;\n\n /**\n * Callback function to handle form submission.\n * This function will receive the parsed and validated form values.\n */\n handler: (values: Static<T>) => unknown;\n\n /**\n * Optional initial values for the form fields.\n * This can be used to pre-populate the form with existing data.\n */\n initialValues?: Partial<Static<T>>;\n\n /**\n * Optional function to create custom field attributes.\n * This can be used to add custom validation, styles, or other attributes.\n */\n onCreateField?: (\n name: keyof Static<T> & string,\n schema: TSchema,\n ) => InputHTMLAttributes<unknown>;\n\n /**\n * If defined, this will generate a unique ID for each field, prefixed with this string.\n *\n * > \"username\" with id=\"form-123\" will become \"form-123-username\".\n *\n * If omitted, IDs will not be generated.\n */\n id?: string;\n\n onError?: (error: Error) => void;\n\n onChange?: (key: string, value: any, store: Record<string, any>) => void;\n\n onReset?: () => void;\n};\n","import type { TObject } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\nimport { type FormCtrlOptions, FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Custom hook to create a form with validation and field management.\n * This hook uses TypeBox schemas to define the structure and validation rules for the form.\n * It provides a way to handle form submission, field creation, and value management.\n *\n * @example\n * ```tsx\n * import { t } from \"alepha\";\n *\n * const form = useForm({\n * schema: t.object({\n * username: t.text(),\n * password: t.text(),\n * }),\n * handler: (values) => {\n * console.log(\"Form submitted with values:\", values);\n * },\n * });\n *\n * return (\n * <form {...form.props}>\n * <input {...form.input.username.props} />\n * <input {...form.input.password.props} />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends TObject>(\n options: FormCtrlOptions<T>,\n deps: any[] = [],\n): FormModel<T> => {\n const alepha = useAlepha();\n const formId = useId();\n const initialValuesRef = useRef(options.initialValues);\n\n const form = useMemo(() => {\n return alepha.inject(FormModel<T>, {\n lifetime: \"transient\",\n args: [options.id || formId, options],\n });\n }, deps);\n\n useEffect(() => {\n if (initialValuesRef.current !== options.initialValues) {\n initialValuesRef.current = options.initialValues;\n if (options.initialValues) {\n form.setInitialValues(options.initialValues as Record<string, any>);\n }\n }\n }, [options.initialValues]);\n\n return form;\n};\n","import type { TObject } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Hook to subscribe to all form values.\n * Re-renders on every field change — use only when needed (debug panels, live previews).\n */\nexport const useFormValues = <T extends TObject>(\n form: FormModel<T>,\n): Record<string, any> => {\n const alepha = useAlepha();\n const [values, setValues] = useState<Record<string, any>>(form.currentValues);\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n return alepha.events.on(\"form:change\", (ev) => {\n if (ev.id === form.id) {\n setValues(form.currentValues);\n }\n });\n }, []);\n\n return values;\n};\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport { default as FormState } from \"./components/FormState.tsx\";\nexport * from \"./errors/FormValidationError.ts\";\nexport * from \"./hooks/useFieldValue.ts\";\nexport * from \"./hooks/useForm.ts\";\nexport * from \"./hooks/useFormState.ts\";\nexport * from \"./hooks/useFormValues.ts\";\nexport * from \"./services/FormModel.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"form:change\": { id: string; path: string; value: any };\n \"form:submit:begin\": { id: string };\n \"form:submit:success\": { id: string; values: Record<string, any> };\n \"form:submit:error\": { id: string; error: Error };\n \"form:submit:end\": { id: string };\n \"form:reset\": { id: string };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Type-safe forms with validation.\n *\n * **Features:**\n * - Form state management\n * - TypeBox schema validation\n * - Field-level error handling\n * - Submit handling with loading state\n * - Form reset\n *\n * @module alepha.react.form\n */\nexport const AlephaReactForm = $module({\n name: \"alepha.react.form\",\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/form/hooks/useFormState.ts","../../../src/react/form/components/FormState.tsx","../../../src/react/form/errors/FormValidationError.ts","../../../src/react/form/hooks/useFieldValue.ts","../../../src/react/form/services/FormModel.ts","../../../src/react/form/hooks/useForm.ts","../../../src/react/form/hooks/useFormValues.ts","../../../src/react/form/services/prettyName.ts","../../../src/react/form/services/parseField.ts","../../../src/react/form/index.ts"],"sourcesContent":["import { type TObject, TypeBoxError } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nexport interface UseFormStateReturn {\n loading: boolean;\n dirty: boolean;\n values?: Record<string, any>;\n error?: Error;\n}\n\nexport const useFormState = <\n T extends TObject,\n Keys extends keyof UseFormStateReturn,\n>(\n target: FormModel<T> | { form: FormModel<T>; path: string },\n _events: Keys[] = [\"loading\", \"dirty\", \"error\"] as Keys[],\n): Pick<UseFormStateReturn, Keys> => {\n const alepha = useAlepha();\n const events = _events as string[];\n\n const [dirty, setDirty] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [values, setValues] = useState<Record<string, any> | undefined>(\n undefined,\n );\n\n const form = \"form\" in target ? target.form : target;\n const path = \"path\" in target ? target.path : undefined;\n\n const hasValues = events.includes(\"values\");\n const hasErrors = events.includes(\"error\");\n const hasDirty = events.includes(\"dirty\");\n const hasLoading = events.includes(\"loading\");\n\n useEffect(() => {\n const listeners: Function[] = [];\n\n if (hasErrors || hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:change\", (event) => {\n if (event.id === form.id) {\n if (!path || event.path === path) {\n if (hasDirty) {\n setDirty(true);\n }\n if (hasErrors) {\n setError(undefined);\n }\n }\n if (hasValues) {\n setValues(form.currentValues);\n }\n }\n }),\n );\n }\n\n if (hasLoading) {\n listeners.push(\n alepha.events.on(\"form:submit:begin\", (event) => {\n if (event.id === form.id) {\n setLoading(true);\n }\n }),\n alepha.events.on(\"form:submit:end\", (event) => {\n if (event.id === form.id) {\n setLoading(false);\n }\n }),\n );\n }\n\n if (hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:submit:success\", (event) => {\n if (event.id === form.id) {\n if (hasValues) {\n setValues(event.values);\n }\n if (hasDirty) {\n setDirty(false);\n }\n }\n }),\n );\n }\n\n if (hasDirty) {\n listeners.push(\n alepha.events.on(\"form:reset\", (event) => {\n if (event.id === form.id) {\n setDirty(false);\n }\n }),\n );\n }\n\n if (hasErrors) {\n listeners.push(\n alepha.events.on(\"form:submit:error\", (event) => {\n if (event.id === form.id) {\n if (\n !path ||\n (event.error instanceof TypeBoxError &&\n event.error.value.path === path)\n ) {\n setError(event.error);\n }\n }\n }),\n );\n }\n\n return () => {\n for (const unsub of listeners) {\n unsub();\n }\n };\n }, []);\n\n return {\n dirty,\n loading,\n error,\n values,\n } as Pick<UseFormStateReturn, Keys>;\n};\n","import type { TObject } from \"alepha\";\nimport type { ReactNode } from \"react\";\nimport { useFormState } from \"../hooks/useFormState.ts\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nconst FormState = <T extends TObject>(props: {\n form: FormModel<T>;\n children: (state: { loading: boolean; dirty: boolean }) => ReactNode;\n}) => {\n const formState = useFormState(props.form);\n return props.children({\n loading: formState.loading,\n dirty: formState.dirty,\n });\n};\n\nexport default FormState;\n","import { TypeBoxError } from \"alepha\";\n\nexport class FormValidationError extends TypeBoxError {\n readonly name = \"ValidationError\";\n\n constructor(options: {\n message: string;\n path: string;\n }) {\n super({\n message: options.message,\n instancePath: options.path,\n schemaPath: \"\",\n keyword: \"not\",\n params: {},\n });\n }\n}\n","import { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { BaseInputField } from \"../services/FormModel.ts\";\n\n/**\n * Hook to subscribe to a single form field's value.\n * Only re-renders when this specific field changes.\n *\n * @returns A tuple of [value, setValue] similar to useState.\n */\nexport const useFieldValue = (\n input: BaseInputField,\n): [any, (value: any) => void] => {\n const alepha = useAlepha();\n const [value, setValue] = useState(input?.initialValue);\n\n useEffect(() => {\n if (!input?.form || !alepha.isBrowser()) {\n return;\n }\n\n return alepha.events.on(\"form:change\", (ev) => {\n if (ev.id === input.form.id && ev.path === input.path) {\n setValue(ev.value);\n }\n });\n }, []);\n\n const setFieldValue = (newValue: any) => {\n input.set(newValue);\n };\n\n return [value, setFieldValue];\n};\n","import type { TArray } from \"alepha\";\nimport {\n $inject,\n Alepha,\n type Static,\n type TObject,\n type TSchema,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { InputHTMLAttributes } from \"react\";\n\n/**\n * FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.\n * It manages form state, handles input changes, and processes form submissions with validation.\n *\n * It means to be injected and used within React components to provide a structured way to create and manage forms.\n *\n * @see {@link useForm}\n */\nexport class FormModel<T extends TObject> {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly values: Record<string, any> = {};\n protected readonly initialValues: Record<string, any> = {};\n protected submitInProgress = false;\n\n public input: SchemaToInput<T>;\n\n public get submitting(): boolean {\n return this.submitInProgress;\n }\n\n constructor(\n public readonly id: string,\n public readonly options: FormCtrlOptions<T>,\n ) {\n this.options = options;\n\n // Initialize with schema defaults first, then override with initialValues\n const schemaDefaults = this.extractSchemaDefaults(options.schema);\n if (Object.keys(schemaDefaults).length > 0) {\n Object.assign(this.values, schemaDefaults);\n }\n\n if (options.initialValues) {\n const decoded = this.alepha.codec.decode(\n options.schema,\n options.initialValues,\n ) as Record<string, any>;\n Object.assign(this.values, decoded);\n }\n\n this.initialValues = { ...this.values };\n\n this.input = this.createProxyFromSchema(options, options.schema, {\n store: this.values,\n parent: \"\",\n });\n }\n\n /**\n * Extract default values from a TypeBox schema.\n * Recursively handles nested objects.\n */\n protected extractSchemaDefaults(\n schema: TObject,\n prefix: string = \"\",\n ): Record<string, any> {\n const defaults: Record<string, any> = {};\n\n if (!schema.properties) {\n return defaults;\n }\n\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (\"default\" in propSchema && propSchema.default !== undefined) {\n defaults[fullKey] = propSchema.default;\n } else if (\n propSchema &&\n \"type\" in propSchema &&\n propSchema.type === \"object\" &&\n \"properties\" in propSchema\n ) {\n // Recursively extract defaults from nested objects\n const nestedDefaults = this.extractSchemaDefaults(\n propSchema as TObject,\n fullKey,\n );\n Object.assign(defaults, nestedDefaults);\n }\n }\n\n return defaults;\n }\n\n public get currentValues(): Record<string, any> {\n return this.restructureValues(this.values);\n }\n\n public get props() {\n return {\n id: this.id,\n noValidate: true,\n onSubmit: (ev?: FormEventLike) => {\n ev?.preventDefault?.();\n this.submit();\n },\n onReset: (event: FormEventLike) => this.reset(event),\n };\n }\n\n public readonly setInitialValues = (values: Record<string, any>) => {\n const decoded = this.alepha.codec.decode(\n this.options.schema,\n values,\n ) as Record<string, any>;\n\n for (const key in this.initialValues) {\n delete (this.initialValues as Record<string, any>)[key];\n }\n Object.assign(this.initialValues, decoded);\n\n for (const key in this.values) {\n delete this.values[key];\n }\n Object.assign(this.values, { ...this.initialValues });\n\n for (const [key, value] of Object.entries(this.values)) {\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path, value },\n { catch: true },\n );\n }\n };\n\n public readonly reset = (event?: FormEventLike) => {\n event?.preventDefault?.();\n for (const key in this.values) {\n delete this.values[key];\n }\n Object.assign(this.values, { ...this.initialValues });\n for (const [key, value] of Object.entries(this.values)) {\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path, value },\n { catch: true },\n );\n }\n this.alepha.events.emit(\"form:reset\", { id: this.id }, { catch: true });\n this.options.onReset?.();\n };\n\n public readonly submit = async () => {\n if (this.submitInProgress) {\n this.log.warn(\n \"Form submission already in progress, ignoring duplicate submit.\",\n );\n return;\n }\n\n // emit both action and form events\n await this.alepha.events.emit(\"react:action:begin\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:begin\", {\n id: this.id,\n });\n\n this.submitInProgress = true;\n\n const options = this.options;\n\n try {\n let values: Record<string, any> = this.restructureValues(this.values);\n\n if (t.schema.isSchema(options.schema)) {\n values = this.alepha.codec.decode(options.schema, values) as Record<\n string,\n any\n >;\n }\n\n await options.handler(values as any);\n\n await this.alepha.events.emit(\"react:action:success\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:success\", {\n id: this.id,\n values,\n });\n } catch (error) {\n this.log.error(\"Form submission error:\", error);\n\n options.onError?.(error as Error);\n\n await this.alepha.events.emit(\"react:action:error\", {\n type: \"form\",\n id: this.id,\n error: error as Error,\n });\n await this.alepha.events.emit(\"form:submit:error\", {\n error: error as Error,\n id: this.id,\n });\n } finally {\n this.submitInProgress = false;\n }\n\n await this.alepha.events.emit(\"react:action:end\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:end\", {\n id: this.id,\n });\n };\n\n /**\n * Restructures flat keys like \"address.city\" into nested objects like { address: { city: ... } }\n * Values are already typed from onChange, so no conversion is needed.\n */\n protected restructureValues(store: Record<string, any>): Record<string, any> {\n const values: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(store)) {\n if (key.includes(\".\")) {\n // nested object: restructure flat key to nested structure\n this.restructureNestedValue(values, key, value);\n } else {\n // value is already typed, just copy it\n values[key] = value;\n }\n }\n\n return values;\n }\n\n /**\n * Helper to restructure a flat key like \"address.city\" into nested object structure.\n * The value is already typed, so we just assign it to the nested path.\n */\n protected restructureNestedValue(\n values: Record<string, any>,\n key: string,\n value: any,\n ) {\n const pathSegments = key.split(\".\");\n const finalPropertyKey = pathSegments.pop();\n if (!finalPropertyKey) {\n return;\n }\n\n let currentObjectLevel = values;\n\n // traverse/create the nested structure\n for (const segment of pathSegments) {\n currentObjectLevel[segment] ??= {};\n currentObjectLevel = currentObjectLevel[segment];\n }\n\n // value is already typed from onChange, just assign it\n currentObjectLevel[finalPropertyKey] = value;\n }\n\n protected createProxyFromSchema<T extends TObject>(\n options: FormCtrlOptions<T>,\n schema: TSchema,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): SchemaToInput<T> {\n const parent = context.parent || \"\";\n return new Proxy<SchemaToInput<T>>({} as SchemaToInput<T>, {\n get: (_, prop: string) => {\n if (!options.schema || !t.schema.isObject(schema)) {\n return {};\n }\n\n if (prop in schema.properties) {\n // // it's a nested object, create another proxy\n // if (t.schema.isObject(schema.properties[prop])) {\n // return this.createProxyFromSchema(\n // options,\n // schema.properties[prop],\n // {\n // parent: parent ? `${parent}.${prop}` : prop,\n // store: context.store,\n // },\n // );\n // }\n\n return this.createInputFromSchema<T>(\n prop as keyof Static<T> & string,\n options,\n schema,\n schema.required?.includes(prop as string) || false,\n context,\n );\n }\n },\n });\n }\n\n protected createInputFromSchema<T extends TObject>(\n name: keyof Static<T> & string,\n options: FormCtrlOptions<T>,\n schema: TObject,\n required: boolean,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): BaseInputField {\n const parent = context.parent || \"\";\n const field = schema.properties?.[name];\n if (!field) {\n return {\n path: \"\",\n required,\n initialValue: undefined,\n props: {} as InputHTMLAttributes<unknown>,\n schema: schema,\n set: () => {},\n form: this,\n };\n }\n\n const isRequired = schema.required?.includes(name) ?? false;\n const key = parent ? `${parent}.${name}` : name;\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n\n const set = (value: any) => {\n const typedValue = this.getValueFromInput(value, field);\n context.store[key] = typedValue;\n if (options.onChange) {\n options.onChange(key, typedValue, context.store);\n }\n this.alepha.events.emit(\n \"form:change\",\n { id: this.id, path: path, value: typedValue },\n { catch: true },\n );\n };\n\n const attr: InputHTMLAttributesLike = {\n name: key,\n };\n\n if (options.id) {\n attr.id = `${options.id}-${key}`;\n (attr as any)[\"data-testid\"] = attr.id;\n }\n\n if (t.schema.isString(field)) {\n if (field.maxLength != null) {\n attr.maxLength = Number(field.maxLength);\n }\n\n if (field.minLength != null) {\n attr.minLength = Number(field.minLength);\n }\n }\n\n if (isRequired) {\n attr.required = true;\n }\n\n if (\"description\" in field && typeof field.description === \"string\") {\n attr[\"aria-label\"] = field.description;\n }\n\n if (t.schema.isInteger(field) || t.schema.isNumber(field)) {\n attr.type = \"number\";\n } else if (name === \"password\") {\n attr.type = \"password\";\n } else if (name === \"email\") {\n attr.type = \"email\";\n } else if (name === \"url\") {\n attr.type = \"url\";\n } else if (t.schema.isString(field)) {\n if (field.format === \"binary\") {\n attr.type = \"file\";\n } else if (field.format === \"date\") {\n attr.type = \"date\";\n } else if (field.format === \"time\") {\n attr.type = \"time\";\n } else if (field.format === \"date-time\") {\n attr.type = \"datetime-local\";\n } else {\n attr.type = \"text\";\n }\n } else if (t.schema.isBoolean(field)) {\n attr.type = \"checkbox\";\n }\n\n if (options.onCreateField) {\n const customAttr = options.onCreateField(name, field);\n Object.assign(attr, customAttr);\n }\n\n // if type = object, add items: { [key: string]: InputField }\n if (t.schema.isObject(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n items: this.createProxyFromSchema(options, field, {\n parent: key,\n store: context.store,\n }),\n } as ObjectInputField<any>;\n }\n\n // if type = array, add items: InputField[]\n if (t.schema.isArray(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n items: [], // <- will be populated dynamically in the UI\n } as ArrayInputField<any>;\n }\n\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n initialValue: context.store[key],\n };\n }\n\n /**\n * Convert an input value to the correct type based on the schema.\n * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)\n */\n protected getValueFromInput(input: any, schema: TSchema): any {\n if (input instanceof File) {\n // for file inputs, return the File object directly\n if (t.schema.isString(schema) && schema.format === \"binary\") {\n return input;\n }\n // for now, ignore other formats\n return null;\n }\n\n if (t.schema.isBoolean(schema)) {\n // Handle string representations from Select components (Yes/No dropdown)\n if (input === \"true\") return true;\n if (input === \"false\") return false;\n if (input === \"\" || input === null || input === undefined)\n return undefined;\n // Handle actual boolean values\n return !!input;\n }\n\n if (t.schema.isNumber(schema)) {\n const num = Number(input);\n return Number.isNaN(num) ? null : num;\n }\n\n if (t.schema.isString(schema)) {\n if (schema.format === \"date\") {\n return new Date(input).toISOString().slice(0, 10); // For date input\n }\n if (schema.format === \"time\") {\n return new Date(`1970-01-01T${input}`).toISOString().slice(11, 16); // For time input\n }\n if (schema.format === \"date-time\") {\n return new Date(input).toISOString(); // For datetime-local input\n }\n return String(input);\n }\n\n return input; // fallback for other types\n }\n}\n\nexport type SchemaToInput<T extends TObject> = {\n [K in keyof T[\"properties\"]]: InputField<T[\"properties\"][K]>;\n};\n\nexport interface FormEventLike {\n preventDefault?: () => void;\n stopPropagation?: () => void;\n}\n\nexport type InputField<T extends TSchema> = T extends TObject\n ? ObjectInputField<T>\n : T extends TArray<infer U>\n ? ArrayInputField<U>\n : BaseInputField;\n\nexport interface BaseInputField {\n path: string;\n required: boolean;\n initialValue: any;\n props: InputHTMLAttributesLike;\n schema: TSchema;\n set: (value: any) => void;\n form: FormModel<any>;\n items?: any;\n}\n\nexport interface ObjectInputField<T extends TObject> extends BaseInputField {\n items: SchemaToInput<T>;\n}\n\nexport interface ArrayInputField<T extends TSchema> extends BaseInputField {\n items: Array<InputField<T>>;\n}\n\nexport type InputHTMLAttributesLike = Pick<\n InputHTMLAttributes<unknown>,\n | \"id\"\n | \"name\"\n | \"type\"\n | \"value\"\n | \"required\"\n | \"maxLength\"\n | \"minLength\"\n | \"aria-label\"\n> & {\n value?: any;\n};\n\nexport type FormCtrlOptions<T extends TObject> = {\n /**\n * The schema defining the structure and validation rules for the form.\n * This should be a TypeBox schema object.\n */\n schema: T;\n\n /**\n * Callback function to handle form submission.\n * This function will receive the parsed and validated form values.\n */\n handler: (values: Static<T>) => unknown;\n\n /**\n * Optional initial values for the form fields.\n * This can be used to pre-populate the form with existing data.\n */\n initialValues?: Partial<Static<T>>;\n\n /**\n * Optional function to create custom field attributes.\n * This can be used to add custom validation, styles, or other attributes.\n */\n onCreateField?: (\n name: keyof Static<T> & string,\n schema: TSchema,\n ) => InputHTMLAttributes<unknown>;\n\n /**\n * If defined, this will generate a unique ID for each field, prefixed with this string.\n *\n * > \"username\" with id=\"form-123\" will become \"form-123-username\".\n *\n * If omitted, IDs will not be generated.\n */\n id?: string;\n\n onError?: (error: Error) => void;\n\n onChange?: (key: string, value: any, store: Record<string, any>) => void;\n\n onReset?: () => void;\n};\n","import type { TObject } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useId, useMemo, useRef } from \"react\";\nimport { type FormCtrlOptions, FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Custom hook to create a form with validation and field management.\n * This hook uses TypeBox schemas to define the structure and validation rules for the form.\n * It provides a way to handle form submission, field creation, and value management.\n *\n * @example\n * ```tsx\n * import { t } from \"alepha\";\n *\n * const form = useForm({\n * schema: t.object({\n * username: t.text(),\n * password: t.text(),\n * }),\n * handler: (values) => {\n * console.log(\"Form submitted with values:\", values);\n * },\n * });\n *\n * return (\n * <form {...form.props}>\n * <input {...form.input.username.props} />\n * <input {...form.input.password.props} />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends TObject>(\n options: FormCtrlOptions<T>,\n deps: any[] = [],\n): FormModel<T> => {\n const alepha = useAlepha();\n const formId = useId();\n const initialValuesRef = useRef(options.initialValues);\n\n const form = useMemo(() => {\n return alepha.inject(FormModel<T>, {\n lifetime: \"transient\",\n args: [options.id || formId, options],\n });\n }, deps);\n\n useEffect(() => {\n if (initialValuesRef.current !== options.initialValues) {\n initialValuesRef.current = options.initialValues;\n if (options.initialValues) {\n form.setInitialValues(options.initialValues as Record<string, any>);\n }\n }\n }, [options.initialValues]);\n\n return form;\n};\n","import type { TObject } from \"alepha\";\nimport { useAlepha } from \"alepha/react\";\nimport { useEffect, useState } from \"react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Hook to subscribe to all form values.\n * Re-renders on every field change — use only when needed (debug panels, live previews).\n */\nexport const useFormValues = <T extends TObject>(\n form: FormModel<T>,\n): Record<string, any> => {\n const alepha = useAlepha();\n const [values, setValues] = useState<Record<string, any>>(form.currentValues);\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n return alepha.events.on(\"form:change\", (ev) => {\n if (ev.id === form.id) {\n setValues(form.currentValues);\n }\n });\n }, []);\n\n return values;\n};\n","/**\n * Converts a path or identifier string into a pretty display name.\n * For paths like \"/contacts/0/name\", extracts just the field name \"Name\".\n * Handles camelCase and snake_case conversion to Title Case.\n *\n * @example\n * prettyName(\"/userName\") // \"User Name\"\n * prettyName(\"/contacts/0/email\") // \"Email\"\n * prettyName(\"/address/streetName\") // \"Street Name\"\n * prettyName(\"first_name\") // \"First Name\"\n */\nexport const prettyName = (name: string): string => {\n const segments = name.split(\"/\").filter((s) => s && !/^\\d+$/.test(s));\n const fieldName = segments[segments.length - 1] || name.replaceAll(\"/\", \"\");\n return fieldName\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n};\n","import { type TSchema, TypeBoxError } from \"alepha\";\nimport type { BaseInputField } from \"./FormModel.ts\";\nimport { prettyName } from \"./prettyName.ts\";\n\n/**\n * Semantic icon hint derived from schema metadata. UI layers map this\n * to their own icon set — this module is headless and ships no JSX.\n */\nexport type IconHint =\n | \"email\"\n | \"password\"\n | \"phone\"\n | \"url\"\n | \"number\"\n | \"calendar\"\n | \"clock\"\n | \"list\"\n | \"text\"\n | \"user\"\n | \"file\"\n | \"switch\";\n\nexport interface FieldConstraints {\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n}\n\nexport interface FieldMeta {\n id?: string;\n label: string;\n description?: string;\n error?: string;\n required: boolean;\n type?: string;\n format?: string;\n isEnum: boolean;\n isArray: boolean;\n isObject: boolean;\n isArrayOfObjects: boolean;\n enum?: readonly unknown[];\n iconHint?: IconHint;\n constraints: FieldConstraints;\n testId?: string;\n schema: TSchema;\n}\n\nexport interface ParseFieldOptions {\n label?: string;\n description?: string;\n error?: Error;\n}\n\n/**\n * Derives a {@link FieldMeta} from an `InputField` (from `useForm`) plus\n * optional overrides. Pure — no React, no JSX, no UI library coupling.\n *\n * UI components consume this metadata to render labels, descriptions,\n * error messages, icons, and validation constraints.\n */\nexport const parseField = (\n input: BaseInputField,\n options: ParseFieldOptions = {},\n): FieldMeta => {\n const schema = input.schema as TSchema & {\n type?: string;\n format?: string;\n title?: string;\n description?: string;\n enum?: readonly unknown[];\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n properties?: unknown;\n items?: { properties?: unknown };\n };\n\n const label =\n options.label ??\n (typeof schema.title === \"string\" ? schema.title : undefined) ??\n prettyName(input.path);\n\n const description =\n options.description ??\n (typeof schema.description === \"string\" ? schema.description : undefined);\n\n const error =\n options.error instanceof TypeBoxError\n ? (options.error as TypeBoxError).value?.message\n : undefined;\n\n const type = schema.type;\n const format = typeof schema.format === \"string\" ? schema.format : undefined;\n const isEnum = Array.isArray(schema.enum);\n const isArray = type === \"array\";\n const isObject = type === \"object\" && Boolean(schema.properties);\n const isArrayOfObjects =\n isArray && Boolean(schema.items && (schema.items as any).properties);\n\n const name = input.props.name;\n const iconHint = inferIconHint({ type, format, name, isEnum, isArray });\n\n const constraints: FieldConstraints = {};\n if (typeof schema.minLength === \"number\")\n constraints.minLength = schema.minLength;\n if (typeof schema.maxLength === \"number\")\n constraints.maxLength = schema.maxLength;\n if (typeof schema.minimum === \"number\") constraints.minimum = schema.minimum;\n if (typeof schema.maximum === \"number\") constraints.maximum = schema.maximum;\n if (typeof schema.pattern === \"string\") constraints.pattern = schema.pattern;\n\n return {\n id: input.props.id,\n label,\n description,\n error,\n required: input.required,\n type,\n format,\n isEnum,\n isArray,\n isObject,\n isArrayOfObjects,\n enum: schema.enum,\n iconHint,\n constraints,\n testId: (input.props as Record<string, unknown>)[\"data-testid\"] as\n | string\n | undefined,\n schema: input.schema,\n };\n};\n\nconst inferIconHint = (params: {\n type?: string;\n format?: string;\n name?: string;\n isEnum: boolean;\n isArray: boolean;\n}): IconHint | undefined => {\n const { type, format, name, isEnum, isArray } = params;\n\n if (format === \"email\") return \"email\";\n if (format === \"url\" || format === \"uri\") return \"url\";\n if (format === \"tel\" || format === \"phone\") return \"phone\";\n if (format === \"date\" || format === \"date-time\") return \"calendar\";\n if (format === \"time\") return \"clock\";\n\n if (name?.toLowerCase().includes(\"password\")) return \"password\";\n if (name?.toLowerCase().includes(\"email\")) return \"email\";\n if (name?.toLowerCase().includes(\"phone\")) return \"phone\";\n\n if (type === \"boolean\") return \"switch\";\n if (type === \"number\" || type === \"integer\") return \"number\";\n if (isEnum || isArray) return \"list\";\n if (type === \"string\") return \"text\";\n\n return undefined;\n};\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport { default as FormState } from \"./components/FormState.tsx\";\nexport * from \"./errors/FormValidationError.ts\";\nexport * from \"./hooks/useFieldValue.ts\";\nexport * from \"./hooks/useForm.ts\";\nexport * from \"./hooks/useFormState.ts\";\nexport * from \"./hooks/useFormValues.ts\";\nexport * from \"./services/FormModel.ts\";\nexport * from \"./services/parseField.ts\";\nexport * from \"./services/prettyName.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"form:change\": { id: string; path: string; value: any };\n \"form:submit:begin\": { id: string };\n \"form:submit:success\": { id: string; values: Record<string, any> };\n \"form:submit:error\": { id: string; error: Error };\n \"form:submit:end\": { id: string };\n \"form:reset\": { id: string };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Type-safe forms with validation.\n *\n * **Features:**\n * - Form state management\n * - TypeBox schema validation\n * - Field-level error handling\n * - Submit handling with loading state\n * - Form reset\n *\n * @module alepha.react.form\n */\nexport const AlephaReactForm = $module({\n name: \"alepha.react.form\",\n});\n"],"mappings":"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"}
@@ -370,8 +370,9 @@ var BrowserHeadProvider = class {
370
370
  if (head.script) for (const it of head.script) this.renderScriptTag(document, it);
371
371
  }
372
372
  renderScriptTag(document, script) {
373
- const el = document.createElement("script");
374
373
  if (typeof script === "string") {
374
+ if (this.findInlineScriptByContent(document, script)) return;
375
+ const el = document.createElement("script");
375
376
  el.textContent = script;
376
377
  document.head.appendChild(el);
377
378
  return;
@@ -379,12 +380,26 @@ var BrowserHeadProvider = class {
379
380
  const { content, ...attrs } = script;
380
381
  if (attrs.src) {
381
382
  if (document.querySelector(`script[src="${attrs.src}"]`)) return;
383
+ } else if (typeof attrs.id === "string") {
384
+ if (document.querySelector(`script#${CSS.escape(attrs.id)}`)) return;
385
+ } else if (content) {
386
+ if (this.findInlineScriptByContent(document, content)) return;
382
387
  }
388
+ const el = document.createElement("script");
383
389
  for (const [key, value] of Object.entries(attrs)) if (value === true) el.setAttribute(key, "");
384
390
  else if (value !== void 0 && value !== false) el.setAttribute(key, String(value));
385
391
  if (content) el.textContent = content;
386
392
  document.head.appendChild(el);
387
393
  }
394
+ /**
395
+ * Find an existing inline `<script>` tag (no `src`) with matching textContent.
396
+ * Used to make `renderScriptTag` idempotent across hydration + navigation,
397
+ * so SSR-emitted global scripts aren't re-appended client-side.
398
+ */
399
+ findInlineScriptByContent(document, content) {
400
+ for (const existing of document.head.querySelectorAll("script:not([src])")) if (existing.textContent === content) return existing;
401
+ return null;
402
+ }
388
403
  renderMetaTag(document, meta) {
389
404
  const { content } = meta;
390
405
  if (meta.property) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/react/head/helpers/SeoExpander.ts","../../../src/react/head/providers/HeadProvider.ts","../../../src/react/head/primitives/$head.ts","../../../src/react/head/providers/BrowserHeadProvider.ts","../../../src/react/head/hooks/useHead.ts","../../../src/react/head/index.browser.ts"],"sourcesContent":["import type { Head, HeadMeta } from \"../interfaces/Head.ts\";\n\n/**\n * Expands Head configuration into SEO meta tags.\n *\n * Generates:\n * - `<meta name=\"description\">` from head.description\n * - `<meta property=\"og:*\">` OpenGraph tags\n * - `<meta name=\"twitter:*\">` Twitter Card tags\n *\n * @example\n * ```ts\n * const helper = new SeoExpander();\n * const { meta, link } = helper.expand({\n * title: \"My App\",\n * description: \"Build amazing apps\",\n * image: \"https://example.com/og.png\",\n * url: \"https://example.com/\",\n * });\n * ```\n */\nexport class SeoExpander {\n public expand(head: Head): {\n meta: HeadMeta[];\n link: Array<{ rel: string; href: string }>;\n } {\n const meta: HeadMeta[] = [];\n const link: Array<{ rel: string; href: string }> = [];\n\n // Only expand SEO if there's meaningful content beyond just title\n const hasSeoContent =\n head.description ||\n head.image ||\n head.url ||\n head.siteName ||\n head.locale ||\n head.type ||\n head.og ||\n head.twitter;\n\n if (!hasSeoContent) {\n return { meta, link };\n }\n\n // Base description\n if (head.description) {\n meta.push({ name: \"description\", content: head.description });\n }\n\n // Canonical URL\n if (head.url) {\n link.push({ rel: \"canonical\", href: head.url });\n }\n\n // OpenGraph tags\n this.expandOpenGraph(head, meta);\n\n // Twitter Card tags\n this.expandTwitter(head, meta);\n\n return { meta, link };\n }\n\n protected expandOpenGraph(head: Head, meta: HeadMeta[]): void {\n const ogTitle = head.og?.title ?? head.title;\n const ogDescription = head.og?.description ?? head.description;\n const ogImage = head.og?.image ?? head.image;\n\n if (head.type || ogTitle) {\n meta.push({ property: \"og:type\", content: head.type ?? \"website\" });\n }\n if (head.url) {\n meta.push({ property: \"og:url\", content: head.url });\n }\n if (ogTitle) {\n meta.push({ property: \"og:title\", content: ogTitle });\n }\n if (ogDescription) {\n meta.push({ property: \"og:description\", content: ogDescription });\n }\n if (ogImage) {\n meta.push({ property: \"og:image\", content: ogImage });\n if (head.imageWidth) {\n meta.push({\n property: \"og:image:width\",\n content: String(head.imageWidth),\n });\n }\n if (head.imageHeight) {\n meta.push({\n property: \"og:image:height\",\n content: String(head.imageHeight),\n });\n }\n if (head.imageAlt) {\n meta.push({ property: \"og:image:alt\", content: head.imageAlt });\n }\n }\n if (head.siteName) {\n meta.push({ property: \"og:site_name\", content: head.siteName });\n }\n if (head.locale) {\n meta.push({ property: \"og:locale\", content: head.locale });\n }\n }\n\n protected expandTwitter(head: Head, meta: HeadMeta[]): void {\n const twitterTitle = head.twitter?.title ?? head.title;\n const twitterDescription = head.twitter?.description ?? head.description;\n const twitterImage = head.twitter?.image ?? head.image;\n\n if (head.twitter?.card || twitterTitle || twitterImage) {\n meta.push({\n name: \"twitter:card\",\n content:\n head.twitter?.card ??\n (twitterImage ? \"summary_large_image\" : \"summary\"),\n });\n }\n if (head.url) {\n meta.push({ name: \"twitter:url\", content: head.url });\n }\n if (twitterTitle) {\n meta.push({ name: \"twitter:title\", content: twitterTitle });\n }\n if (twitterDescription) {\n meta.push({ name: \"twitter:description\", content: twitterDescription });\n }\n if (twitterImage) {\n meta.push({ name: \"twitter:image\", content: twitterImage });\n if (head.imageAlt) {\n meta.push({ name: \"twitter:image:alt\", content: head.imageAlt });\n }\n }\n if (head.twitter?.site) {\n meta.push({ name: \"twitter:site\", content: head.twitter.site });\n }\n if (head.twitter?.creator) {\n meta.push({ name: \"twitter:creator\", content: head.twitter.creator });\n }\n }\n}\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { SeoExpander } from \"../helpers/SeoExpander.ts\";\nimport type { Head } from \"../interfaces/Head.ts\";\n\n/**\n * Provides methods to fill and merge head information into the application state.\n *\n * Used both on server and client side to manage document head.\n *\n * @see {@link SeoExpander}\n * @see {@link ServerHeadProvider}\n * @see {@link BrowserHeadProvider}\n */\nexport class HeadProvider {\n protected readonly log = $logger();\n protected readonly seoExpander = $inject(SeoExpander);\n\n public global?: Array<Head | (() => Head)> = [];\n\n /**\n * Track if we've warned about page-level htmlAttributes to avoid spam.\n */\n protected warnedAboutHtmlAttributes = false;\n\n /**\n * Resolve global head configuration (from $head primitives only).\n *\n * This is used to get htmlAttributes early, before page loaders run.\n * Only htmlAttributes from global $head are allowed; page-level htmlAttributes\n * are ignored for early streaming optimization.\n *\n * @returns Merged global head with htmlAttributes\n */\n public resolveGlobalHead(): Head {\n const head: Head = {\n htmlAttributes: { lang: \"en\" },\n };\n\n for (const h of this.global ?? []) {\n const resolved = typeof h === \"function\" ? h() : h;\n if (resolved.htmlAttributes) {\n head.htmlAttributes = {\n ...head.htmlAttributes,\n ...resolved.htmlAttributes,\n };\n }\n }\n\n return head;\n }\n\n /**\n * Fully resolve all global $head entries (functions re-evaluated, objects kept as-is).\n *\n * Unlike resolveGlobalHead() which only extracts htmlAttributes for streaming,\n * this resolves all head properties (meta, link, script, htmlAttributes, etc.).\n *\n * Used by BrowserHeadProvider.refreshGlobalHead() to re-apply global head to the DOM.\n */\n public resolveGlobal(): Head {\n let head: Head = {};\n\n for (const h of this.global ?? []) {\n const resolved = typeof h === \"function\" ? h() : h;\n const { meta, link } = this.seoExpander.expand(resolved);\n head = {\n ...head,\n ...resolved,\n meta: [...(head.meta ?? []), ...meta, ...(resolved.meta ?? [])],\n link: [...(head.link ?? []), ...link, ...(resolved.link ?? [])],\n script: [...(head.script ?? []), ...(resolved.script ?? [])],\n };\n }\n\n return head;\n }\n\n public fillHead(state: HeadState) {\n state.head = {\n ...state.head,\n };\n\n for (const h of this.global ?? []) {\n const head = typeof h === \"function\" ? h() : h;\n this.mergeHead(state, head);\n }\n\n for (const layer of state.layers) {\n if (layer.route?.head && !layer.error) {\n this.fillHeadByPage(layer.route, state, layer.props ?? {});\n }\n }\n\n // Defaults if none were set by global $head or page head\n state.head.title ??= \"App\";\n state.head.htmlAttributes = {\n lang: \"en\",\n ...state.head.htmlAttributes,\n };\n }\n\n protected mergeHead(state: HeadState, head: Head): void {\n // Expand SEO fields into meta tags\n const { meta, link } = this.seoExpander.expand(head);\n state.head = {\n ...state.head,\n ...head,\n meta: [...(state.head.meta ?? []), ...meta, ...(head.meta ?? [])],\n link: [...(state.head.link ?? []), ...link, ...(head.link ?? [])],\n script: [...(state.head.script ?? []), ...(head.script ?? [])],\n };\n }\n\n protected fillHeadByPage(\n page: HeadRoute,\n state: HeadState,\n props: Record<string, any>,\n ): void {\n if (!page.head) {\n return;\n }\n\n state.head ??= {};\n\n const head =\n typeof page.head === \"function\"\n ? page.head(props, state.head)\n : page.head;\n\n // Expand SEO fields into meta tags\n const { meta, link } = this.seoExpander.expand(head);\n state.head.meta = [...(state.head.meta ?? []), ...meta];\n state.head.link = [...(state.head.link ?? []), ...link];\n\n if (head.title) {\n state.head ??= {};\n\n if (state.head.titleSeparator) {\n state.head.title = `${head.title}${state.head.titleSeparator}${state.head.title}`;\n } else {\n state.head.title = head.title;\n }\n\n state.head.titleSeparator = head.titleSeparator;\n }\n\n // htmlAttributes from pages are ignored for early streaming optimization.\n // Only global $head can set htmlAttributes.\n if (head.htmlAttributes && !this.warnedAboutHtmlAttributes) {\n this.warnedAboutHtmlAttributes = true;\n this.log.warn(\n \"Page-level htmlAttributes are ignored. Use global $head() for htmlAttributes instead, \" +\n \"as they are sent before page loaders run for early streaming optimization.\",\n );\n }\n\n if (head.bodyAttributes) {\n state.head.bodyAttributes = {\n ...state.head.bodyAttributes,\n ...head.bodyAttributes,\n };\n }\n\n if (head.meta) {\n state.head.meta = [...(state.head.meta ?? []), ...(head.meta ?? [])];\n }\n\n if (head.link) {\n state.head.link = [...(state.head.link ?? []), ...(head.link ?? [])];\n }\n\n if (head.script) {\n state.head.script = [\n ...(state.head.script ?? []),\n ...(head.script ?? []),\n ];\n }\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Minimal route interface for head processing.\n * Avoids circular dependency with alepha/react/router.\n */\ninterface HeadRoute {\n head?: Head | ((props: Record<string, any>, previous?: Head) => Head);\n}\n\n/**\n * Minimal state interface for head processing.\n * Avoids circular dependency with alepha/react/router.\n */\ninterface HeadState {\n head: Head;\n layers: Array<{\n route?: HeadRoute;\n props?: Record<string, any>;\n error?: Error;\n }>;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"../providers/HeadProvider.ts\";\n\n/**\n * Set global `<head>` options for the application.\n */\nexport const $head = (options: HeadPrimitiveOptions) => {\n return createPrimitive(HeadPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type HeadPrimitiveOptions = Head | (() => Head);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {\n protected readonly provider = $inject(HeadProvider);\n protected onInit() {\n this.provider.global = [...(this.provider.global ?? []), this.options];\n }\n}\n\n$head[KIND] = HeadPrimitive;\n","import { $inject, Alepha } from \"alepha\";\nimport type { Head, HeadMeta } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\n/**\n * Browser-side head provider that manages document head elements.\n *\n * Used by ReactBrowserProvider and ReactBrowserRouterProvider to update\n * document title, meta tags, and other head elements during client-side\n * navigation.\n */\nexport class BrowserHeadProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly headProvider = $inject(HeadProvider);\n\n protected get document(): Document {\n return window.document;\n }\n\n /**\n * Fill head state from route configurations and render to document.\n * Combines fillHead from HeadProvider with renderHead to the DOM.\n *\n * Only runs in browser environment - no-op on server.\n */\n public fillAndRenderHead(state: { head: Head; layers: Array<any> }): void {\n // Skip on server-side\n if (!this.alepha.isBrowser()) {\n return;\n }\n\n this.headProvider.fillHead(state as any);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n }\n\n /**\n * Re-evaluate all global $head entries and apply the result to the DOM.\n *\n * Call this when something that affects global $head output changes at runtime\n * (e.g., theme switch). Page-level head (title, meta from routes) is not touched.\n */\n public refreshGlobalHead(): void {\n const head = this.headProvider.resolveGlobal();\n this.renderHead(this.document, head);\n }\n\n public getHead(document: Document): Head {\n return {\n get title() {\n return document.title;\n },\n get htmlAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.documentElement.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get bodyAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.body.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get meta() {\n const metas: HeadMeta[] = [];\n // Get meta tags with name attribute\n for (const meta of document.head.querySelectorAll(\"meta[name]\")) {\n const name = meta.getAttribute(\"name\");\n const content = meta.getAttribute(\"content\");\n if (name && content) {\n metas.push({ name, content });\n }\n }\n // Get meta tags with property attribute (OpenGraph)\n for (const meta of document.head.querySelectorAll(\"meta[property]\")) {\n const property = meta.getAttribute(\"property\");\n const content = meta.getAttribute(\"content\");\n if (property && content) {\n metas.push({ property, content });\n }\n }\n return metas;\n },\n };\n }\n\n public renderHead(document: Document, head: Head): void {\n if (head.title) {\n document.title = head.title;\n }\n\n if (head.bodyAttributes) {\n for (const [key, value] of Object.entries(head.bodyAttributes)) {\n if (value) {\n document.body.setAttribute(key, value);\n } else {\n document.body.removeAttribute(key);\n }\n }\n }\n\n if (head.htmlAttributes) {\n for (const [key, value] of Object.entries(head.htmlAttributes)) {\n if (value) {\n document.documentElement.setAttribute(key, value);\n } else {\n document.documentElement.removeAttribute(key);\n }\n }\n }\n\n if (head.meta) {\n for (const it of head.meta) {\n this.renderMetaTag(document, it);\n }\n }\n\n if (head.link) {\n for (const it of head.link) {\n const { rel, href } = it;\n let link = document.querySelector(`link[rel=\"${rel}\"][href=\"${href}\"]`);\n if (!link) {\n link = document.createElement(\"link\");\n link.setAttribute(\"rel\", rel);\n link.setAttribute(\"href\", href);\n if (it.type) {\n link.setAttribute(\"type\", it.type);\n }\n if (it.as) {\n link.setAttribute(\"as\", it.as);\n }\n if (it.crossorigin != null) {\n link.setAttribute(\"crossorigin\", \"\");\n }\n document.head.appendChild(link);\n }\n }\n }\n\n if (head.script) {\n for (const it of head.script) {\n this.renderScriptTag(document, it);\n }\n }\n }\n\n protected renderScriptTag(\n document: Document,\n script:\n | string\n | (Record<string, string | boolean | undefined> & { content?: string }),\n ): void {\n const el = document.createElement(\"script\");\n\n // Handle plain string as inline script\n if (typeof script === \"string\") {\n el.textContent = script;\n document.head.appendChild(el);\n return;\n }\n\n const { content, ...attrs } = script;\n\n // For scripts with src, check if already exists\n if (attrs.src) {\n const existing = document.querySelector(`script[src=\"${attrs.src}\"]`);\n if (existing) {\n return;\n }\n }\n\n for (const [key, value] of Object.entries(attrs)) {\n if (value === true) {\n el.setAttribute(key, \"\");\n } else if (value !== undefined && value !== false) {\n el.setAttribute(key, String(value));\n }\n }\n\n if (content) {\n el.textContent = content;\n }\n\n document.head.appendChild(el);\n }\n\n protected renderMetaTag(document: Document, meta: HeadMeta): void {\n const { content } = meta;\n\n // Handle OpenGraph tags (property attribute)\n if (meta.property) {\n const existing = document.querySelector(\n `meta[property=\"${meta.property}\"]`,\n );\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"property\", meta.property);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n return;\n }\n\n // Handle standard meta tags (name attribute)\n if (meta.name) {\n const existing = document.querySelector(`meta[name=\"${meta.name}\"]`);\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"name\", meta.name);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n }\n }\n}\n","import { Alepha } from \"alepha\";\nimport { useInject } from \"alepha/react\";\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { BrowserHeadProvider } from \"../providers/BrowserHeadProvider.ts\";\n\n/**\n * ```tsx\n * const App = () => {\n * const [head, setHead] = useHead({\n * // will set the document title on the first render\n * title: \"My App\",\n * });\n *\n * return (\n * // This will update the document title when the button is clicked\n * <button onClick={() => setHead({ title: \"Change Title\" })}>\n * Change Title {head.title}\n * </button>\n * );\n * }\n * ```\n */\nexport const useHead = (options?: UseHeadOptions): UseHeadReturn => {\n const alepha = useInject(Alepha);\n\n const current = useMemo(() => {\n if (!alepha.isBrowser()) {\n return {};\n }\n\n return alepha.inject(BrowserHeadProvider).getHead(window.document);\n }, []);\n\n const setHead = useCallback((head?: Head | ((previous?: Head) => Head)) => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const headProvider = alepha.inject(BrowserHeadProvider);\n const resolved =\n typeof head === \"function\"\n ? head(headProvider.getHead(window.document))\n : head || {};\n headProvider.renderHead(window.document, resolved);\n }, []);\n\n useEffect(() => {\n if (options) {\n setHead(options);\n }\n }, []);\n\n return [current, setHead];\n};\n\nexport type UseHeadOptions = Head | ((previous?: Head) => Head);\n\nexport type UseHeadReturn = [\n Head,\n (head?: Head | ((previous?: Head) => Head)) => void,\n];\n","import { $module } from \"alepha\";\nimport { AlephaReact } from \"alepha/react\";\nimport { $head } from \"./primitives/$head.ts\";\nimport { BrowserHeadProvider } from \"./providers/BrowserHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./helpers/SeoExpander.ts\";\nexport * from \"./hooks/useHead.ts\";\nexport * from \"./interfaces/Head.ts\";\nexport * from \"./primitives/$head.ts\";\nexport * from \"./providers/BrowserHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Alepha React Head Module\n *\n * @see {@link BrowserHeadProvider}\n * @module alepha.react.head\n */\nexport const AlephaReactHead = $module({\n name: \"alepha.react.head\",\n primitives: [$head],\n services: [AlephaReact, BrowserHeadProvider],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/react/head/helpers/SeoExpander.ts","../../../src/react/head/providers/HeadProvider.ts","../../../src/react/head/primitives/$head.ts","../../../src/react/head/providers/BrowserHeadProvider.ts","../../../src/react/head/hooks/useHead.ts","../../../src/react/head/index.browser.ts"],"sourcesContent":["import type { Head, HeadMeta } from \"../interfaces/Head.ts\";\n\n/**\n * Expands Head configuration into SEO meta tags.\n *\n * Generates:\n * - `<meta name=\"description\">` from head.description\n * - `<meta property=\"og:*\">` OpenGraph tags\n * - `<meta name=\"twitter:*\">` Twitter Card tags\n *\n * @example\n * ```ts\n * const helper = new SeoExpander();\n * const { meta, link } = helper.expand({\n * title: \"My App\",\n * description: \"Build amazing apps\",\n * image: \"https://example.com/og.png\",\n * url: \"https://example.com/\",\n * });\n * ```\n */\nexport class SeoExpander {\n public expand(head: Head): {\n meta: HeadMeta[];\n link: Array<{ rel: string; href: string }>;\n } {\n const meta: HeadMeta[] = [];\n const link: Array<{ rel: string; href: string }> = [];\n\n // Only expand SEO if there's meaningful content beyond just title\n const hasSeoContent =\n head.description ||\n head.image ||\n head.url ||\n head.siteName ||\n head.locale ||\n head.type ||\n head.og ||\n head.twitter;\n\n if (!hasSeoContent) {\n return { meta, link };\n }\n\n // Base description\n if (head.description) {\n meta.push({ name: \"description\", content: head.description });\n }\n\n // Canonical URL\n if (head.url) {\n link.push({ rel: \"canonical\", href: head.url });\n }\n\n // OpenGraph tags\n this.expandOpenGraph(head, meta);\n\n // Twitter Card tags\n this.expandTwitter(head, meta);\n\n return { meta, link };\n }\n\n protected expandOpenGraph(head: Head, meta: HeadMeta[]): void {\n const ogTitle = head.og?.title ?? head.title;\n const ogDescription = head.og?.description ?? head.description;\n const ogImage = head.og?.image ?? head.image;\n\n if (head.type || ogTitle) {\n meta.push({ property: \"og:type\", content: head.type ?? \"website\" });\n }\n if (head.url) {\n meta.push({ property: \"og:url\", content: head.url });\n }\n if (ogTitle) {\n meta.push({ property: \"og:title\", content: ogTitle });\n }\n if (ogDescription) {\n meta.push({ property: \"og:description\", content: ogDescription });\n }\n if (ogImage) {\n meta.push({ property: \"og:image\", content: ogImage });\n if (head.imageWidth) {\n meta.push({\n property: \"og:image:width\",\n content: String(head.imageWidth),\n });\n }\n if (head.imageHeight) {\n meta.push({\n property: \"og:image:height\",\n content: String(head.imageHeight),\n });\n }\n if (head.imageAlt) {\n meta.push({ property: \"og:image:alt\", content: head.imageAlt });\n }\n }\n if (head.siteName) {\n meta.push({ property: \"og:site_name\", content: head.siteName });\n }\n if (head.locale) {\n meta.push({ property: \"og:locale\", content: head.locale });\n }\n }\n\n protected expandTwitter(head: Head, meta: HeadMeta[]): void {\n const twitterTitle = head.twitter?.title ?? head.title;\n const twitterDescription = head.twitter?.description ?? head.description;\n const twitterImage = head.twitter?.image ?? head.image;\n\n if (head.twitter?.card || twitterTitle || twitterImage) {\n meta.push({\n name: \"twitter:card\",\n content:\n head.twitter?.card ??\n (twitterImage ? \"summary_large_image\" : \"summary\"),\n });\n }\n if (head.url) {\n meta.push({ name: \"twitter:url\", content: head.url });\n }\n if (twitterTitle) {\n meta.push({ name: \"twitter:title\", content: twitterTitle });\n }\n if (twitterDescription) {\n meta.push({ name: \"twitter:description\", content: twitterDescription });\n }\n if (twitterImage) {\n meta.push({ name: \"twitter:image\", content: twitterImage });\n if (head.imageAlt) {\n meta.push({ name: \"twitter:image:alt\", content: head.imageAlt });\n }\n }\n if (head.twitter?.site) {\n meta.push({ name: \"twitter:site\", content: head.twitter.site });\n }\n if (head.twitter?.creator) {\n meta.push({ name: \"twitter:creator\", content: head.twitter.creator });\n }\n }\n}\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { SeoExpander } from \"../helpers/SeoExpander.ts\";\nimport type { Head } from \"../interfaces/Head.ts\";\n\n/**\n * Provides methods to fill and merge head information into the application state.\n *\n * Used both on server and client side to manage document head.\n *\n * @see {@link SeoExpander}\n * @see {@link ServerHeadProvider}\n * @see {@link BrowserHeadProvider}\n */\nexport class HeadProvider {\n protected readonly log = $logger();\n protected readonly seoExpander = $inject(SeoExpander);\n\n public global?: Array<Head | (() => Head)> = [];\n\n /**\n * Track if we've warned about page-level htmlAttributes to avoid spam.\n */\n protected warnedAboutHtmlAttributes = false;\n\n /**\n * Resolve global head configuration (from $head primitives only).\n *\n * This is used to get htmlAttributes early, before page loaders run.\n * Only htmlAttributes from global $head are allowed; page-level htmlAttributes\n * are ignored for early streaming optimization.\n *\n * @returns Merged global head with htmlAttributes\n */\n public resolveGlobalHead(): Head {\n const head: Head = {\n htmlAttributes: { lang: \"en\" },\n };\n\n for (const h of this.global ?? []) {\n const resolved = typeof h === \"function\" ? h() : h;\n if (resolved.htmlAttributes) {\n head.htmlAttributes = {\n ...head.htmlAttributes,\n ...resolved.htmlAttributes,\n };\n }\n }\n\n return head;\n }\n\n /**\n * Fully resolve all global $head entries (functions re-evaluated, objects kept as-is).\n *\n * Unlike resolveGlobalHead() which only extracts htmlAttributes for streaming,\n * this resolves all head properties (meta, link, script, htmlAttributes, etc.).\n *\n * Used by BrowserHeadProvider.refreshGlobalHead() to re-apply global head to the DOM.\n */\n public resolveGlobal(): Head {\n let head: Head = {};\n\n for (const h of this.global ?? []) {\n const resolved = typeof h === \"function\" ? h() : h;\n const { meta, link } = this.seoExpander.expand(resolved);\n head = {\n ...head,\n ...resolved,\n meta: [...(head.meta ?? []), ...meta, ...(resolved.meta ?? [])],\n link: [...(head.link ?? []), ...link, ...(resolved.link ?? [])],\n script: [...(head.script ?? []), ...(resolved.script ?? [])],\n };\n }\n\n return head;\n }\n\n public fillHead(state: HeadState) {\n state.head = {\n ...state.head,\n };\n\n for (const h of this.global ?? []) {\n const head = typeof h === \"function\" ? h() : h;\n this.mergeHead(state, head);\n }\n\n for (const layer of state.layers) {\n if (layer.route?.head && !layer.error) {\n this.fillHeadByPage(layer.route, state, layer.props ?? {});\n }\n }\n\n // Defaults if none were set by global $head or page head\n state.head.title ??= \"App\";\n state.head.htmlAttributes = {\n lang: \"en\",\n ...state.head.htmlAttributes,\n };\n }\n\n protected mergeHead(state: HeadState, head: Head): void {\n // Expand SEO fields into meta tags\n const { meta, link } = this.seoExpander.expand(head);\n state.head = {\n ...state.head,\n ...head,\n meta: [...(state.head.meta ?? []), ...meta, ...(head.meta ?? [])],\n link: [...(state.head.link ?? []), ...link, ...(head.link ?? [])],\n script: [...(state.head.script ?? []), ...(head.script ?? [])],\n };\n }\n\n protected fillHeadByPage(\n page: HeadRoute,\n state: HeadState,\n props: Record<string, any>,\n ): void {\n if (!page.head) {\n return;\n }\n\n state.head ??= {};\n\n const head =\n typeof page.head === \"function\"\n ? page.head(props, state.head)\n : page.head;\n\n // Expand SEO fields into meta tags\n const { meta, link } = this.seoExpander.expand(head);\n state.head.meta = [...(state.head.meta ?? []), ...meta];\n state.head.link = [...(state.head.link ?? []), ...link];\n\n if (head.title) {\n state.head ??= {};\n\n if (state.head.titleSeparator) {\n state.head.title = `${head.title}${state.head.titleSeparator}${state.head.title}`;\n } else {\n state.head.title = head.title;\n }\n\n state.head.titleSeparator = head.titleSeparator;\n }\n\n // htmlAttributes from pages are ignored for early streaming optimization.\n // Only global $head can set htmlAttributes.\n if (head.htmlAttributes && !this.warnedAboutHtmlAttributes) {\n this.warnedAboutHtmlAttributes = true;\n this.log.warn(\n \"Page-level htmlAttributes are ignored. Use global $head() for htmlAttributes instead, \" +\n \"as they are sent before page loaders run for early streaming optimization.\",\n );\n }\n\n if (head.bodyAttributes) {\n state.head.bodyAttributes = {\n ...state.head.bodyAttributes,\n ...head.bodyAttributes,\n };\n }\n\n if (head.meta) {\n state.head.meta = [...(state.head.meta ?? []), ...(head.meta ?? [])];\n }\n\n if (head.link) {\n state.head.link = [...(state.head.link ?? []), ...(head.link ?? [])];\n }\n\n if (head.script) {\n state.head.script = [\n ...(state.head.script ?? []),\n ...(head.script ?? []),\n ];\n }\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Minimal route interface for head processing.\n * Avoids circular dependency with alepha/react/router.\n */\ninterface HeadRoute {\n head?: Head | ((props: Record<string, any>, previous?: Head) => Head);\n}\n\n/**\n * Minimal state interface for head processing.\n * Avoids circular dependency with alepha/react/router.\n */\ninterface HeadState {\n head: Head;\n layers: Array<{\n route?: HeadRoute;\n props?: Record<string, any>;\n error?: Error;\n }>;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"../providers/HeadProvider.ts\";\n\n/**\n * Set global `<head>` options for the application.\n */\nexport const $head = (options: HeadPrimitiveOptions) => {\n return createPrimitive(HeadPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type HeadPrimitiveOptions = Head | (() => Head);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {\n protected readonly provider = $inject(HeadProvider);\n protected onInit() {\n this.provider.global = [...(this.provider.global ?? []), this.options];\n }\n}\n\n$head[KIND] = HeadPrimitive;\n","import { $inject, Alepha } from \"alepha\";\nimport type { Head, HeadMeta } from \"../interfaces/Head.ts\";\nimport { HeadProvider } from \"./HeadProvider.ts\";\n\n/**\n * Browser-side head provider that manages document head elements.\n *\n * Used by ReactBrowserProvider and ReactBrowserRouterProvider to update\n * document title, meta tags, and other head elements during client-side\n * navigation.\n */\nexport class BrowserHeadProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly headProvider = $inject(HeadProvider);\n\n protected get document(): Document {\n return window.document;\n }\n\n /**\n * Fill head state from route configurations and render to document.\n * Combines fillHead from HeadProvider with renderHead to the DOM.\n *\n * Only runs in browser environment - no-op on server.\n */\n public fillAndRenderHead(state: { head: Head; layers: Array<any> }): void {\n // Skip on server-side\n if (!this.alepha.isBrowser()) {\n return;\n }\n\n this.headProvider.fillHead(state as any);\n if (state.head) {\n this.renderHead(this.document, state.head);\n }\n }\n\n /**\n * Re-evaluate all global $head entries and apply the result to the DOM.\n *\n * Call this when something that affects global $head output changes at runtime\n * (e.g., theme switch). Page-level head (title, meta from routes) is not touched.\n */\n public refreshGlobalHead(): void {\n const head = this.headProvider.resolveGlobal();\n this.renderHead(this.document, head);\n }\n\n public getHead(document: Document): Head {\n return {\n get title() {\n return document.title;\n },\n get htmlAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.documentElement.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get bodyAttributes() {\n const attrs: Record<string, string> = {};\n for (const attr of document.body.attributes) {\n attrs[attr.name] = attr.value;\n }\n return attrs;\n },\n get meta() {\n const metas: HeadMeta[] = [];\n // Get meta tags with name attribute\n for (const meta of document.head.querySelectorAll(\"meta[name]\")) {\n const name = meta.getAttribute(\"name\");\n const content = meta.getAttribute(\"content\");\n if (name && content) {\n metas.push({ name, content });\n }\n }\n // Get meta tags with property attribute (OpenGraph)\n for (const meta of document.head.querySelectorAll(\"meta[property]\")) {\n const property = meta.getAttribute(\"property\");\n const content = meta.getAttribute(\"content\");\n if (property && content) {\n metas.push({ property, content });\n }\n }\n return metas;\n },\n };\n }\n\n public renderHead(document: Document, head: Head): void {\n if (head.title) {\n document.title = head.title;\n }\n\n if (head.bodyAttributes) {\n for (const [key, value] of Object.entries(head.bodyAttributes)) {\n if (value) {\n document.body.setAttribute(key, value);\n } else {\n document.body.removeAttribute(key);\n }\n }\n }\n\n if (head.htmlAttributes) {\n for (const [key, value] of Object.entries(head.htmlAttributes)) {\n if (value) {\n document.documentElement.setAttribute(key, value);\n } else {\n document.documentElement.removeAttribute(key);\n }\n }\n }\n\n if (head.meta) {\n for (const it of head.meta) {\n this.renderMetaTag(document, it);\n }\n }\n\n if (head.link) {\n for (const it of head.link) {\n const { rel, href } = it;\n let link = document.querySelector(`link[rel=\"${rel}\"][href=\"${href}\"]`);\n if (!link) {\n link = document.createElement(\"link\");\n link.setAttribute(\"rel\", rel);\n link.setAttribute(\"href\", href);\n if (it.type) {\n link.setAttribute(\"type\", it.type);\n }\n if (it.as) {\n link.setAttribute(\"as\", it.as);\n }\n if (it.crossorigin != null) {\n link.setAttribute(\"crossorigin\", \"\");\n }\n document.head.appendChild(link);\n }\n }\n }\n\n if (head.script) {\n for (const it of head.script) {\n this.renderScriptTag(document, it);\n }\n }\n }\n\n protected renderScriptTag(\n document: Document,\n script:\n | string\n | (Record<string, string | boolean | undefined> & { content?: string }),\n ): void {\n // Plain string → inline script. Dedupe by exact content match against\n // any existing inline script (handles SSR-emitted globals that would\n // otherwise be re-appended on hydration).\n if (typeof script === \"string\") {\n if (this.findInlineScriptByContent(document, script)) return;\n const el = document.createElement(\"script\");\n el.textContent = script;\n document.head.appendChild(el);\n return;\n }\n\n const { content, ...attrs } = script;\n\n // src-based scripts: dedupe by src attribute (existing behaviour).\n if (attrs.src) {\n if (document.querySelector(`script[src=\"${attrs.src}\"]`)) return;\n } else if (typeof attrs.id === \"string\") {\n // id-based dedupe — single source of truth per id.\n if (document.querySelector(`script#${CSS.escape(attrs.id)}`)) return;\n } else if (content) {\n // Inline scripts with `content` and no src/id: fall back to content match.\n if (this.findInlineScriptByContent(document, content)) return;\n }\n\n const el = document.createElement(\"script\");\n for (const [key, value] of Object.entries(attrs)) {\n if (value === true) {\n el.setAttribute(key, \"\");\n } else if (value !== undefined && value !== false) {\n el.setAttribute(key, String(value));\n }\n }\n if (content) {\n el.textContent = content;\n }\n document.head.appendChild(el);\n }\n\n /**\n * Find an existing inline `<script>` tag (no `src`) with matching textContent.\n * Used to make `renderScriptTag` idempotent across hydration + navigation,\n * so SSR-emitted global scripts aren't re-appended client-side.\n */\n protected findInlineScriptByContent(\n document: Document,\n content: string,\n ): Element | null {\n for (const existing of document.head.querySelectorAll(\n \"script:not([src])\",\n )) {\n if (existing.textContent === content) return existing;\n }\n return null;\n }\n\n protected renderMetaTag(document: Document, meta: HeadMeta): void {\n const { content } = meta;\n\n // Handle OpenGraph tags (property attribute)\n if (meta.property) {\n const existing = document.querySelector(\n `meta[property=\"${meta.property}\"]`,\n );\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"property\", meta.property);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n return;\n }\n\n // Handle standard meta tags (name attribute)\n if (meta.name) {\n const existing = document.querySelector(`meta[name=\"${meta.name}\"]`);\n if (existing) {\n existing.setAttribute(\"content\", content);\n } else {\n const newMeta = document.createElement(\"meta\");\n newMeta.setAttribute(\"name\", meta.name);\n newMeta.setAttribute(\"content\", content);\n document.head.appendChild(newMeta);\n }\n }\n }\n}\n","import { Alepha } from \"alepha\";\nimport { useInject } from \"alepha/react\";\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport type { Head } from \"../interfaces/Head.ts\";\nimport { BrowserHeadProvider } from \"../providers/BrowserHeadProvider.ts\";\n\n/**\n * ```tsx\n * const App = () => {\n * const [head, setHead] = useHead({\n * // will set the document title on the first render\n * title: \"My App\",\n * });\n *\n * return (\n * // This will update the document title when the button is clicked\n * <button onClick={() => setHead({ title: \"Change Title\" })}>\n * Change Title {head.title}\n * </button>\n * );\n * }\n * ```\n */\nexport const useHead = (options?: UseHeadOptions): UseHeadReturn => {\n const alepha = useInject(Alepha);\n\n const current = useMemo(() => {\n if (!alepha.isBrowser()) {\n return {};\n }\n\n return alepha.inject(BrowserHeadProvider).getHead(window.document);\n }, []);\n\n const setHead = useCallback((head?: Head | ((previous?: Head) => Head)) => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const headProvider = alepha.inject(BrowserHeadProvider);\n const resolved =\n typeof head === \"function\"\n ? head(headProvider.getHead(window.document))\n : head || {};\n headProvider.renderHead(window.document, resolved);\n }, []);\n\n useEffect(() => {\n if (options) {\n setHead(options);\n }\n }, []);\n\n return [current, setHead];\n};\n\nexport type UseHeadOptions = Head | ((previous?: Head) => Head);\n\nexport type UseHeadReturn = [\n Head,\n (head?: Head | ((previous?: Head) => Head)) => void,\n];\n","import { $module } from \"alepha\";\nimport { AlephaReact } from \"alepha/react\";\nimport { $head } from \"./primitives/$head.ts\";\nimport { BrowserHeadProvider } from \"./providers/BrowserHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./helpers/SeoExpander.ts\";\nexport * from \"./hooks/useHead.ts\";\nexport * from \"./interfaces/Head.ts\";\nexport * from \"./primitives/$head.ts\";\nexport * from \"./providers/BrowserHeadProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Alepha React Head Module\n *\n * @see {@link BrowserHeadProvider}\n * @module alepha.react.head\n */\nexport const AlephaReactHead = $module({\n name: \"alepha.react.head\",\n primitives: [$head],\n services: [AlephaReact, BrowserHeadProvider],\n});\n"],"mappings":"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"}
@@ -322,6 +322,12 @@ declare class BrowserHeadProvider {
322
322
  protected renderScriptTag(document: Document, script: string | (Record<string, string | boolean | undefined> & {
323
323
  content?: string;
324
324
  })): void;
325
+ /**
326
+ * Find an existing inline `<script>` tag (no `src`) with matching textContent.
327
+ * Used to make `renderScriptTag` idempotent across hydration + navigation,
328
+ * so SSR-emitted global scripts aren't re-appended client-side.
329
+ */
330
+ protected findInlineScriptByContent(document: Document, content: string): Element | null;
325
331
  protected renderMetaTag(document: Document, meta: HeadMeta): void;
326
332
  }
327
333
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/head/interfaces/Head.ts","../../../src/react/head/helpers/SeoExpander.ts","../../../src/react/head/hooks/useHead.ts","../../../src/react/head/providers/HeadProvider.ts","../../../src/react/head/primitives/$head.ts","../../../src/react/head/providers/BrowserHeadProvider.ts","../../../src/react/head/providers/ServerHeadProvider.ts","../../../src/react/head/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/head/interfaces/Head.ts","../../../src/react/head/helpers/SeoExpander.ts","../../../src/react/head/hooks/useHead.ts","../../../src/react/head/providers/HeadProvider.ts","../../../src/react/head/primitives/$head.ts","../../../src/react/head/providers/BrowserHeadProvider.ts","../../../src/react/head/providers/ServerHeadProvider.ts","../../../src/react/head/index.ts"],"mappings":"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"}