@vantageos/mosaic 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +88 -2
  2. package/LICENSE +110 -0
  3. package/README.md +193 -21
  4. package/dist/PipelineBoard-FucyGArR.d.cts +759 -0
  5. package/dist/PipelineBoard-FucyGArR.d.ts +759 -0
  6. package/dist/PipelineBoard.markdown-DIcvrS-2.d.cts +16 -0
  7. package/dist/PipelineBoard.markdown-DIcvrS-2.d.ts +16 -0
  8. package/dist/{StatusBadge.schema-1tOjB4Ir.d.cts → StatusBadge.schema-BEjSH2_4.d.cts} +2 -2
  9. package/dist/{StatusBadge.schema-1tOjB4Ir.d.ts → StatusBadge.schema-BEjSH2_4.d.ts} +2 -2
  10. package/dist/{Tabs.schema-BzcEyeq8.d.cts → Tabs.schema-Dqr6vwV3.d.cts} +2 -2
  11. package/dist/{Tabs.schema-BzcEyeq8.d.ts → Tabs.schema-Dqr6vwV3.d.ts} +2 -2
  12. package/dist/Timeline-Coo_3tA_.d.cts +114 -0
  13. package/dist/Timeline-Coo_3tA_.d.ts +114 -0
  14. package/dist/Timeline.markdown-C6UPDv1H.d.cts +4 -0
  15. package/dist/Timeline.markdown-C6UPDv1H.d.ts +4 -0
  16. package/dist/TransactionPreview-BzWDn8kE.d.cts +391 -0
  17. package/dist/TransactionPreview-BzWDn8kE.d.ts +391 -0
  18. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.cts +7 -0
  19. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.ts +7 -0
  20. package/dist/artifacts.cjs +190 -2
  21. package/dist/artifacts.cjs.map +1 -1
  22. package/dist/artifacts.js +190 -2
  23. package/dist/artifacts.js.map +1 -1
  24. package/dist/confirmation.cjs +728 -2
  25. package/dist/confirmation.cjs.map +1 -1
  26. package/dist/confirmation.d.cts +2 -1
  27. package/dist/confirmation.d.ts +2 -1
  28. package/dist/confirmation.js +726 -4
  29. package/dist/confirmation.js.map +1 -1
  30. package/dist/display.cjs +1555 -203
  31. package/dist/display.cjs.map +1 -1
  32. package/dist/display.d.cts +6 -229
  33. package/dist/display.d.ts +6 -229
  34. package/dist/display.js +1539 -206
  35. package/dist/display.js.map +1 -1
  36. package/dist/forms.cjs +139 -46
  37. package/dist/forms.cjs.map +1 -1
  38. package/dist/forms.d.cts +2 -2
  39. package/dist/forms.d.ts +2 -2
  40. package/dist/forms.js +139 -46
  41. package/dist/forms.js.map +1 -1
  42. package/dist/host.cjs +822 -0
  43. package/dist/host.cjs.map +1 -0
  44. package/dist/host.d.cts +287 -0
  45. package/dist/host.d.ts +287 -0
  46. package/dist/host.js +794 -0
  47. package/dist/host.js.map +1 -0
  48. package/dist/{index-CwXVgtGb.d.cts → index-BadRcCbm.d.cts} +28 -11
  49. package/dist/{index-CwXVgtGb.d.ts → index-BadRcCbm.d.ts} +28 -11
  50. package/dist/index.cjs +2202 -247
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +42 -8
  53. package/dist/index.d.ts +42 -8
  54. package/dist/index.js +2167 -251
  55. package/dist/index.js.map +1 -1
  56. package/dist/input.d.cts +2 -2
  57. package/dist/input.d.ts +2 -2
  58. package/dist/media.cjs +190 -2
  59. package/dist/media.cjs.map +1 -1
  60. package/dist/media.d.cts +1 -1
  61. package/dist/media.d.ts +1 -1
  62. package/dist/media.js +190 -2
  63. package/dist/media.js.map +1 -1
  64. package/dist/preact/artifacts.js +190 -2
  65. package/dist/preact/artifacts.js.map +1 -1
  66. package/dist/preact/confirmation.d.ts +321 -1
  67. package/dist/preact/confirmation.js +629 -4
  68. package/dist/preact/confirmation.js.map +1 -1
  69. package/dist/preact/display.d.ts +570 -31
  70. package/dist/preact/display.js +1248 -126
  71. package/dist/preact/display.js.map +1 -1
  72. package/dist/preact/forms.d.ts +32 -15
  73. package/dist/preact/forms.js +139 -46
  74. package/dist/preact/forms.js.map +1 -1
  75. package/dist/preact/host.d.ts +307 -0
  76. package/dist/preact/host.js +875 -0
  77. package/dist/preact/host.js.map +1 -0
  78. package/dist/preact/media.d.ts +37 -1
  79. package/dist/preact/media.js +423 -0
  80. package/dist/preact/media.js.map +1 -1
  81. package/dist/preact/progress.d.ts +92 -1
  82. package/dist/preact/progress.js +469 -3
  83. package/dist/preact/progress.js.map +1 -1
  84. package/dist/progress.cjs +525 -2
  85. package/dist/progress.cjs.map +1 -1
  86. package/dist/progress.d.cts +2 -1
  87. package/dist/progress.d.ts +2 -1
  88. package/dist/progress.js +522 -3
  89. package/dist/progress.js.map +1 -1
  90. package/dist/react/artifacts.cjs +190 -2
  91. package/dist/react/artifacts.cjs.map +1 -1
  92. package/dist/react/artifacts.js +190 -2
  93. package/dist/react/artifacts.js.map +1 -1
  94. package/dist/react/confirmation.cjs +630 -2
  95. package/dist/react/confirmation.cjs.map +1 -1
  96. package/dist/react/confirmation.d.cts +3 -3
  97. package/dist/react/confirmation.d.ts +3 -3
  98. package/dist/react/confirmation.js +629 -4
  99. package/dist/react/confirmation.js.map +1 -1
  100. package/dist/react/display.cjs +1137 -123
  101. package/dist/react/display.cjs.map +1 -1
  102. package/dist/react/display.d.cts +42 -14
  103. package/dist/react/display.d.ts +42 -14
  104. package/dist/react/display.js +1122 -126
  105. package/dist/react/display.js.map +1 -1
  106. package/dist/react/forms.cjs +139 -46
  107. package/dist/react/forms.cjs.map +1 -1
  108. package/dist/react/forms.d.cts +1 -1
  109. package/dist/react/forms.d.ts +1 -1
  110. package/dist/react/forms.js +139 -46
  111. package/dist/react/forms.js.map +1 -1
  112. package/dist/react/host.cjs +910 -0
  113. package/dist/react/host.cjs.map +1 -0
  114. package/dist/react/host.d.cts +24 -0
  115. package/dist/react/host.d.ts +24 -0
  116. package/dist/react/host.js +875 -0
  117. package/dist/react/host.js.map +1 -0
  118. package/dist/react/input.d.cts +1 -1
  119. package/dist/react/input.d.ts +1 -1
  120. package/dist/react/media.cjs +426 -0
  121. package/dist/react/media.cjs.map +1 -1
  122. package/dist/react/media.d.cts +19 -1
  123. package/dist/react/media.d.ts +19 -1
  124. package/dist/react/media.js +423 -0
  125. package/dist/react/media.js.map +1 -1
  126. package/dist/react/progress.cjs +471 -2
  127. package/dist/react/progress.cjs.map +1 -1
  128. package/dist/react/progress.d.cts +1 -1
  129. package/dist/react/progress.d.ts +1 -1
  130. package/dist/react/progress.js +469 -3
  131. package/dist/react/progress.js.map +1 -1
  132. package/dist/server/resource.cjs +151 -0
  133. package/dist/server/resource.cjs.map +1 -0
  134. package/dist/server/resource.d.cts +90 -0
  135. package/dist/server/resource.d.ts +90 -0
  136. package/dist/server/resource.js +147 -0
  137. package/dist/server/resource.js.map +1 -0
  138. package/dist/server.cjs +1100 -25
  139. package/dist/server.cjs.map +1 -1
  140. package/dist/server.d.cts +84 -5
  141. package/dist/server.d.ts +84 -5
  142. package/dist/server.js +1087 -27
  143. package/dist/server.js.map +1 -1
  144. package/dist/tokens.cjs +73 -22
  145. package/dist/tokens.cjs.map +1 -1
  146. package/dist/tokens.d.cts +23 -3
  147. package/dist/tokens.d.ts +23 -3
  148. package/dist/tokens.js +73 -22
  149. package/dist/tokens.js.map +1 -1
  150. package/package.json +250 -231
  151. package/registry.yaml +51 -2
  152. package/dist/Alert.schema-LPJE5gFP.d.cts +0 -71
  153. package/dist/Alert.schema-LPJE5gFP.d.ts +0 -71
  154. package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +0 -22
  155. package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +0 -22
  156. package/dist/{ConfirmDialog-B3CQQGmJ.d.cts → ConfirmDialog-BCtOcmHe.d.cts} +1 -1
  157. package/dist/{ConfirmDialog-B3CQQGmJ.d.ts → ConfirmDialog-BCtOcmHe.d.ts} +1 -1
@@ -45,6 +45,8 @@ var EN = {
45
45
  "TokenDisplayOnceModal.copied": "Copied",
46
46
  "StatusBadge.aria.status": "Status",
47
47
  "StatusBadge.error.invalidProps": "StatusBadge: invalid props",
48
+ "Badge.aria.label": "Badge",
49
+ "Badge.error.invalidProps": "Badge: invalid props",
48
50
  "EmptyState.error.invalidProps": "EmptyState: invalid props",
49
51
  "Toast.aria.close": "Close notification",
50
52
  "Toast.error.invalidProps": "Toast: invalid props",
@@ -114,7 +116,99 @@ var EN = {
114
116
  "Select.search.placeholder": "Search\u2026",
115
117
  "Select.empty": "No options available",
116
118
  "Select.error.required": "Select an option",
117
- "Select.error.invalidProps": "Select: invalid props"
119
+ "Select.error.invalidProps": "Select: invalid props",
120
+ "ModelContext.selection.one": "1 row selected",
121
+ "ModelContext.selection.many": "{n} rows selected",
122
+ "ModelContext.selection.none": "No rows selected",
123
+ "TableView.sort.by": "Sort by",
124
+ "TableView.select.row": "Select row",
125
+ "TableView.select.all": "Select all rows",
126
+ "TableView.loading": "Loading table",
127
+ "StatCard.title": "Statistic",
128
+ "StatCard.trend.up": "Up",
129
+ "StatCard.trend.down": "Down",
130
+ "StatCard.trend.flat": "Flat",
131
+ "StatCard.empty.message": "No statistic to display",
132
+ "StatCard.loading": "Loading statistic",
133
+ "StatCard.error.invalidProps": "StatCard: invalid props",
134
+ "BalanceCard.title": "Balance",
135
+ "BalanceCard.network": "Network",
136
+ "BalanceCard.testnet": "Testnet",
137
+ "BalanceCard.address": "Address",
138
+ "BalanceCard.contract": "Token contract",
139
+ "BalanceCard.value": "Value",
140
+ "BalanceCard.copy": "Copy address",
141
+ "BalanceCard.copied": "Address copied",
142
+ "BalanceCard.empty.message": "No balance to display",
143
+ "BalanceCard.loading": "Loading balance",
144
+ "BalanceCard.error.invalidProps": "BalanceCard: invalid props",
145
+ "Timeline.title": "Timeline",
146
+ "Timeline.status.done": "Done",
147
+ "Timeline.status.current": "In progress",
148
+ "Timeline.status.pending": "Pending",
149
+ "Timeline.status.blocked": "Blocked",
150
+ "Timeline.completed": "{n} completed",
151
+ "Timeline.more": "{n} more",
152
+ "Timeline.empty.message": "No steps to display",
153
+ "Timeline.loading": "Loading timeline",
154
+ "Timeline.error.invalidProps": "Timeline: invalid props",
155
+ "MessageFeed.title": "Messages",
156
+ "MessageFeed.unread": "Unread",
157
+ "MessageFeed.attachment": "Attachment",
158
+ "MessageFeed.showing": "Showing the latest {n} of {total}",
159
+ "MessageFeed.empty.message": "No messages",
160
+ "MessageFeed.loading": "Loading messages",
161
+ "MessageFeed.error.invalidProps": "MessageFeed: invalid props",
162
+ "PipelineBoard.title": "Pipeline",
163
+ "PipelineBoard.deals.one": "1 deal",
164
+ "PipelineBoard.deals.many": "{n} deals",
165
+ "PipelineBoard.total": "Total",
166
+ "PipelineBoard.stage.empty": "No deals",
167
+ "PipelineBoard.filter.label": "Show stages",
168
+ "PipelineBoard.summary.open": "Open full board",
169
+ "PipelineBoard.more": "{n} more stages",
170
+ "PipelineBoard.empty.message": "No pipeline to display",
171
+ "PipelineBoard.loading": "Loading pipeline",
172
+ "PipelineBoard.error.invalidProps": "PipelineBoard: invalid props",
173
+ "PipelineBoard.col.stage": "Stage",
174
+ "PipelineBoard.col.deals": "Deals",
175
+ "TransactionPreview.title": "Transaction preview",
176
+ "TransactionPreview.notice": "NOT SIGNED - preparation only",
177
+ "TransactionPreview.kind.transfer": "Transfer",
178
+ "TransactionPreview.kind.swap": "Swap",
179
+ "TransactionPreview.testnet": "Testnet",
180
+ "TransactionPreview.field.network": "Network",
181
+ "TransactionPreview.field.from": "From",
182
+ "TransactionPreview.field.to": "To",
183
+ "TransactionPreview.field.amount": "Amount",
184
+ "TransactionPreview.field.fee": "Network fee",
185
+ "TransactionPreview.field.sell": "Sell",
186
+ "TransactionPreview.field.buy": "Buy",
187
+ "TransactionPreview.field.minReceived": "Minimum received",
188
+ "TransactionPreview.field.route": "Route",
189
+ "TransactionPreview.field.priceImpact": "Price impact",
190
+ "TransactionPreview.field.expires": "Quote expires",
191
+ "TransactionPreview.priceImpact.high": "High price impact",
192
+ "TransactionPreview.simulation.success": "Simulation succeeded",
193
+ "TransactionPreview.simulation.failed": "Simulation failed",
194
+ "TransactionPreview.simulation.unavailable": "Simulation unavailable",
195
+ "TransactionPreview.warnings": "Warnings",
196
+ "TransactionPreview.handoff": "Open in wallet",
197
+ "TransactionPreview.empty.message": "No transaction to preview",
198
+ "TransactionPreview.loading": "Loading preview",
199
+ "TransactionPreview.error.invalidProps": "TransactionPreview: invalid props",
200
+ "ProgressBar.title": "Progress",
201
+ "ConfirmDialog.title": "Confirmation",
202
+ "ConfirmDialog.options": "Answers",
203
+ "TableView.title": "Data table",
204
+ "TableView.markdown.more": "{n} more rows",
205
+ "MarkdownRenderer.title": "Markdown content",
206
+ "TokenDisplayOnceModal.title": "Token (shown once)",
207
+ "StatusBadge.title": "Status",
208
+ "TokenDisplayOnceModal.notice.once": "The value is shown once, in the app view only.",
209
+ "TokenDisplayOnceModal.field.scope": "Scope",
210
+ "TokenDisplayOnceModal.field.expires": "Expires",
211
+ "TokenDisplayOnceModal.field.fingerprint": "Fingerprint"
118
212
  };
119
213
  var FR = {
120
214
  // ─── Non-forms components ──────────────────────────────────────────────────
@@ -137,6 +231,8 @@ var FR = {
137
231
  "TokenDisplayOnceModal.copied": "Copi\xE9",
138
232
  "StatusBadge.aria.status": "Statut",
139
233
  "StatusBadge.error.invalidProps": "StatusBadge : propri\xE9t\xE9s invalides",
234
+ "Badge.aria.label": "Badge",
235
+ "Badge.error.invalidProps": "Badge : propri\xE9t\xE9s invalides",
140
236
  "EmptyState.error.invalidProps": "EmptyState : propri\xE9t\xE9s invalides",
141
237
  "Toast.aria.close": "Fermer la notification",
142
238
  "Toast.error.invalidProps": "Toast : propri\xE9t\xE9s invalides",
@@ -206,7 +302,99 @@ var FR = {
206
302
  "Select.search.placeholder": "Rechercher\u2026",
207
303
  "Select.empty": "Aucune option disponible",
208
304
  "Select.error.required": "S\xE9lectionnez une option",
209
- "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides"
305
+ "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides",
306
+ "ModelContext.selection.one": "1 ligne s\xE9lectionn\xE9e",
307
+ "ModelContext.selection.many": "{n} lignes s\xE9lectionn\xE9es",
308
+ "ModelContext.selection.none": "Aucune ligne s\xE9lectionn\xE9e",
309
+ "TableView.sort.by": "Trier par",
310
+ "TableView.select.row": "S\xE9lectionner la ligne",
311
+ "TableView.select.all": "Tout s\xE9lectionner",
312
+ "TableView.loading": "Chargement du tableau",
313
+ "StatCard.title": "Statistique",
314
+ "StatCard.trend.up": "En hausse",
315
+ "StatCard.trend.down": "En baisse",
316
+ "StatCard.trend.flat": "Stable",
317
+ "StatCard.empty.message": "Aucune statistique \xE0 afficher",
318
+ "StatCard.loading": "Chargement de la statistique",
319
+ "StatCard.error.invalidProps": "StatCard : propri\xE9t\xE9s invalides",
320
+ "BalanceCard.title": "Solde",
321
+ "BalanceCard.network": "R\xE9seau",
322
+ "BalanceCard.testnet": "R\xE9seau de test",
323
+ "BalanceCard.address": "Adresse",
324
+ "BalanceCard.contract": "Contrat du jeton",
325
+ "BalanceCard.value": "Valeur",
326
+ "BalanceCard.copy": "Copier l'adresse",
327
+ "BalanceCard.copied": "Adresse copi\xE9e",
328
+ "BalanceCard.empty.message": "Aucun solde \xE0 afficher",
329
+ "BalanceCard.loading": "Chargement du solde",
330
+ "BalanceCard.error.invalidProps": "BalanceCard : propri\xE9t\xE9s invalides",
331
+ "Timeline.title": "Chronologie",
332
+ "Timeline.status.done": "Termin\xE9",
333
+ "Timeline.status.current": "En cours",
334
+ "Timeline.status.pending": "\xC0 venir",
335
+ "Timeline.status.blocked": "Bloqu\xE9",
336
+ "Timeline.completed": "{n} termin\xE9(s)",
337
+ "Timeline.more": "{n} de plus",
338
+ "Timeline.empty.message": "Aucune \xE9tape \xE0 afficher",
339
+ "Timeline.loading": "Chargement de la chronologie",
340
+ "Timeline.error.invalidProps": "Timeline : propri\xE9t\xE9s invalides",
341
+ "MessageFeed.title": "Messages",
342
+ "MessageFeed.unread": "Non lu",
343
+ "MessageFeed.attachment": "Pi\xE8ce jointe",
344
+ "MessageFeed.showing": "Affichage des {n} derniers sur {total}",
345
+ "MessageFeed.empty.message": "Aucun message",
346
+ "MessageFeed.loading": "Chargement des messages",
347
+ "MessageFeed.error.invalidProps": "MessageFeed : propri\xE9t\xE9s invalides",
348
+ "PipelineBoard.title": "Pipeline",
349
+ "PipelineBoard.deals.one": "1 affaire",
350
+ "PipelineBoard.deals.many": "{n} affaires",
351
+ "PipelineBoard.total": "Total",
352
+ "PipelineBoard.stage.empty": "Aucune affaire",
353
+ "PipelineBoard.filter.label": "Afficher les \xE9tapes",
354
+ "PipelineBoard.summary.open": "Ouvrir le tableau complet",
355
+ "PipelineBoard.more": "{n} \xE9tapes de plus",
356
+ "PipelineBoard.empty.message": "Aucun pipeline \xE0 afficher",
357
+ "PipelineBoard.loading": "Chargement du pipeline",
358
+ "PipelineBoard.error.invalidProps": "PipelineBoard : propri\xE9t\xE9s invalides",
359
+ "PipelineBoard.col.stage": "\xC9tape",
360
+ "PipelineBoard.col.deals": "Affaires",
361
+ "TransactionPreview.title": "Aper\xE7u de transaction",
362
+ "TransactionPreview.notice": "NON SIGN\xC9 - pr\xE9paration uniquement",
363
+ "TransactionPreview.kind.transfer": "Transfert",
364
+ "TransactionPreview.kind.swap": "\xC9change",
365
+ "TransactionPreview.testnet": "R\xE9seau de test",
366
+ "TransactionPreview.field.network": "R\xE9seau",
367
+ "TransactionPreview.field.from": "De",
368
+ "TransactionPreview.field.to": "\xC0",
369
+ "TransactionPreview.field.amount": "Montant",
370
+ "TransactionPreview.field.fee": "Frais de r\xE9seau",
371
+ "TransactionPreview.field.sell": "Vendre",
372
+ "TransactionPreview.field.buy": "Acheter",
373
+ "TransactionPreview.field.minReceived": "Minimum re\xE7u",
374
+ "TransactionPreview.field.route": "Itin\xE9raire",
375
+ "TransactionPreview.field.priceImpact": "Impact sur le prix",
376
+ "TransactionPreview.field.expires": "Expiration du devis",
377
+ "TransactionPreview.priceImpact.high": "Impact sur le prix \xE9lev\xE9",
378
+ "TransactionPreview.simulation.success": "Simulation r\xE9ussie",
379
+ "TransactionPreview.simulation.failed": "Simulation \xE9chou\xE9e",
380
+ "TransactionPreview.simulation.unavailable": "Simulation indisponible",
381
+ "TransactionPreview.warnings": "Avertissements",
382
+ "TransactionPreview.handoff": "Ouvrir dans le portefeuille",
383
+ "TransactionPreview.empty.message": "Aucune transaction \xE0 pr\xE9visualiser",
384
+ "TransactionPreview.loading": "Chargement de l'aper\xE7u",
385
+ "TransactionPreview.error.invalidProps": "TransactionPreview : propri\xE9t\xE9s invalides",
386
+ "ProgressBar.title": "Progression",
387
+ "ConfirmDialog.title": "Confirmation",
388
+ "ConfirmDialog.options": "R\xE9ponses",
389
+ "TableView.title": "Tableau de donn\xE9es",
390
+ "TableView.markdown.more": "{n} lignes de plus",
391
+ "MarkdownRenderer.title": "Contenu Markdown",
392
+ "TokenDisplayOnceModal.title": "Jeton (affich\xE9 une fois)",
393
+ "StatusBadge.title": "Statut",
394
+ "TokenDisplayOnceModal.notice.once": "La valeur n'est affich\xE9e qu'une fois, dans la vue de l'application uniquement.",
395
+ "TokenDisplayOnceModal.field.scope": "Port\xE9e",
396
+ "TokenDisplayOnceModal.field.expires": "Expire le",
397
+ "TokenDisplayOnceModal.field.fingerprint": "Empreinte"
210
398
  };
211
399
  var TABLE = {
212
400
  en: EN,
@@ -522,92 +710,226 @@ function StatusBadge(raw) {
522
710
  return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("StatusBadge.error.invalidProps", locale) });
523
711
  }
524
712
  }
713
+ var BadgePropsSchema = zod.z.object({
714
+ label: zod.z.string(),
715
+ variant: zod.z.enum(["default", "primary", "success", "warning", "danger", "info", "neutral"]).default("default"),
716
+ dot: zod.z.boolean().optional(),
717
+ count: zod.z.number().int().nonnegative().optional(),
718
+ locale: zod.z.enum(["en", "fr"]).default("en")
719
+ });
720
+ function validateBadgeProps(raw) {
721
+ return BadgePropsSchema.parse(raw);
722
+ }
723
+ var variantClasses2 = {
724
+ default: "bg-slate-100 text-slate-700 ring-1 ring-slate-200",
725
+ primary: "bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300",
726
+ success: "bg-green-100 text-green-800 ring-1 ring-green-300",
727
+ warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
728
+ danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
729
+ info: "bg-blue-100 text-blue-800 ring-1 ring-blue-300",
730
+ neutral: "bg-slate-100 text-slate-800 ring-1 ring-slate-300"
731
+ };
732
+ var dotClasses = {
733
+ default: "bg-slate-400",
734
+ primary: "bg-indigo-500",
735
+ success: "bg-green-500",
736
+ warning: "bg-amber-500",
737
+ danger: "bg-red-500",
738
+ info: "bg-blue-500",
739
+ neutral: "bg-slate-400"
740
+ };
741
+ function BadgeInner({ label, variant, dot, count, locale }) {
742
+ const ariaLabel = t("Badge.aria.label", locale);
743
+ const displayCount = count !== void 0 ? ` (${count})` : "";
744
+ return /* @__PURE__ */ jsxRuntime.jsxs(
745
+ "span",
746
+ {
747
+ "aria-label": `${ariaLabel}: ${label}${displayCount}`,
748
+ lang: locale,
749
+ className: `inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses2[variant]}`,
750
+ children: [
751
+ dot && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: `h-1.5 w-1.5 rounded-full ${dotClasses[variant]}` }),
752
+ label,
753
+ count !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "font-semibold tabular-nums", children: count })
754
+ ]
755
+ }
756
+ );
757
+ }
758
+ function Badge(raw) {
759
+ try {
760
+ const props = validateBadgeProps(raw);
761
+ return /* @__PURE__ */ jsxRuntime.jsx(BadgeInner, { ...props });
762
+ } catch {
763
+ const locale = raw.locale === "fr" ? "fr" : "en";
764
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("Badge.error.invalidProps", locale) });
765
+ }
766
+ }
767
+
768
+ // src/components/display/TableView.logic.ts
769
+ function isMissing(v) {
770
+ return v === null || v === void 0 || v === "";
771
+ }
772
+ var DECIMAL = /^(-)?(\d+)(?:\.(\d+))?$/;
773
+ function parseDecimal(s) {
774
+ const m = DECIMAL.exec(s.trim());
775
+ if (!m) return void 0;
776
+ const int = (m[2] ?? "0").replace(/^0+(?=\d)/, "");
777
+ const frac = (m[3] ?? "").replace(/0+$/, "");
778
+ const zero = int === "0" && frac === "";
779
+ return { neg: m[1] === "-" && !zero, int, frac };
780
+ }
781
+ function compareDecimalStrings(a, b) {
782
+ const x = parseDecimal(a);
783
+ const y = parseDecimal(b);
784
+ if (!x || !y) return 0;
785
+ if (x.neg !== y.neg) return x.neg ? -1 : 1;
786
+ const sign = x.neg ? -1 : 1;
787
+ if (x.int.length !== y.int.length) return sign * (x.int.length < y.int.length ? -1 : 1);
788
+ if (x.int !== y.int) return sign * (x.int < y.int ? -1 : 1);
789
+ const len = Math.max(x.frac.length, y.frac.length);
790
+ const fx = x.frac.padEnd(len, "0");
791
+ const fy = y.frac.padEnd(len, "0");
792
+ if (fx === fy) return 0;
793
+ return sign * (fx < fy ? -1 : 1);
794
+ }
795
+ var collators = /* @__PURE__ */ new Map();
796
+ function collator(locale) {
797
+ let c = collators.get(locale);
798
+ if (!c) {
799
+ c = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
800
+ collators.set(locale, c);
801
+ }
802
+ return c;
803
+ }
804
+ function compareCells(a, b, kind, locale) {
805
+ if (kind === "amount") return compareDecimalStrings(String(a), String(b));
806
+ if (kind === "number") {
807
+ const x = Number(a);
808
+ const y = Number(b);
809
+ if (Number.isNaN(x) || Number.isNaN(y)) return collator(locale).compare(String(a), String(b));
810
+ return x === y ? 0 : x < y ? -1 : 1;
811
+ }
812
+ return collator(locale).compare(String(a), String(b));
813
+ }
814
+ function sortItems(items, getValue, kind, direction, locale) {
815
+ const sign = direction === "asc" ? 1 : -1;
816
+ return items.map((item, index) => ({ item, index, value: getValue(item) })).sort((p, q) => {
817
+ const pm = isMissing(p.value);
818
+ const qm = isMissing(q.value);
819
+ if (pm || qm) return pm && qm ? p.index - q.index : pm ? 1 : -1;
820
+ const c = compareCells(p.value, q.value, kind, locale);
821
+ return c !== 0 ? sign * c : p.index - q.index;
822
+ }).map((e) => e.item);
823
+ }
824
+ function nextSort(current, key) {
825
+ if (!current || current.key !== key) return { key, direction: "asc" };
826
+ return current.direction === "asc" ? { key, direction: "desc" } : void 0;
827
+ }
828
+ function toggleSelection(current, key, mode) {
829
+ if (mode === "none") return new Set(current);
830
+ if (mode === "single") return current.has(key) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([key]);
831
+ const next = new Set(current);
832
+ if (!next.delete(key)) next.add(key);
833
+ return next;
834
+ }
835
+ function toggleAll(current, allKeys) {
836
+ return allKeys.length > 0 && allKeys.every((k) => current.has(k)) ? /* @__PURE__ */ new Set() : new Set(allKeys);
837
+ }
838
+ var ColumnKindSchema = zod.z.enum(["text", "number", "amount", "icon"]);
839
+ var AlignSchema = zod.z.enum(["start", "center", "end"]);
525
840
  var ColumnDefSchema = zod.z.object({
526
841
  key: zod.z.string().min(1),
527
- header: zod.z.string().min(1)
842
+ header: zod.z.string().min(1),
843
+ /** Cell kind: drives comparison and rendering. `amount` = decimal STRING, never a float. */
844
+ type: ColumnKindSchema.default("text"),
845
+ /** Defaults to `end` for `number` / `amount` columns, `start` otherwise. */
846
+ align: AlignSchema.optional(),
847
+ /** CSS width: a number is pixels. */
848
+ width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
849
+ sortable: zod.z.boolean().default(false),
850
+ /**
851
+ * Values of this column are masked in the A-1 markdown fallback and kept out of the HTML
852
+ * props of a tool result (delivered via the result `_meta`), never written to `content[]`.
853
+ */
854
+ sensitive: zod.z.boolean().default(false)
528
855
  // render is a runtime function — excluded from Zod, typed separately
529
856
  });
857
+ var SortSchema = zod.z.object({ key: zod.z.string().min(1), direction: zod.z.enum(["asc", "desc"]) });
530
858
  var TableViewPropsSchema = zod.z.object({
531
859
  columns: zod.z.array(ColumnDefSchema).min(1, "TableView requires at least one column"),
860
+ /**
861
+ * Serialisable rows, for views built through `createMosaicResource` (the React props carry
862
+ * `rows` separately).
863
+ */
864
+ rows: zod.z.array(zod.z.record(zod.z.unknown())).default([]),
532
865
  virtualizeThreshold: zod.z.number().int().positive().default(100),
533
866
  ariaLabel: zod.z.string().min(1),
534
- locale: zod.z.enum(["en", "fr"]).default("en")
867
+ locale: zod.z.enum(["en", "fr"]).default("en"),
868
+ selectionMode: zod.z.enum(["none", "single", "multiple"]).default("none"),
869
+ /** Initial sort. */
870
+ sort: SortSchema.optional(),
871
+ stickyHeader: zod.z.boolean().default(false),
872
+ /** Scroll region height when `stickyHeader` is on (pixels). */
873
+ maxHeight: zod.z.number().int().positive().default(400),
874
+ density: zod.z.enum(["comfortable", "compact"]).default("comfortable"),
875
+ loading: zod.z.boolean().default(false)
535
876
  });
536
877
  function validateTableViewProps(raw) {
537
878
  return TableViewPropsSchema.parse(raw);
538
879
  }
539
880
  var OVERSCAN = 5;
540
- function TableView(props) {
541
- let validatedThreshold = 100;
881
+ var SKELETON_ROWS = ["head", "r1", "r2", "r3", "r4", "r5"];
882
+ function resolveLocale(l) {
883
+ return l === "fr" ? "fr" : "en";
884
+ }
885
+ function validate(props) {
542
886
  try {
543
887
  const validated = validateTableViewProps({
544
- columns: props.columns.map(({ key, header }) => ({ key, header })),
888
+ columns: props.columns.map(({ key, header, type, align, width, sortable }) => ({
889
+ key,
890
+ header,
891
+ type,
892
+ align,
893
+ width,
894
+ sortable
895
+ })),
545
896
  virtualizeThreshold: props.virtualizeThreshold,
546
897
  ariaLabel: props.ariaLabel,
547
- locale: props.locale
898
+ locale: props.locale,
899
+ selectionMode: props.selectionMode,
900
+ density: props.density
548
901
  });
549
- validatedThreshold = validated.virtualizeThreshold;
902
+ return validated.virtualizeThreshold;
550
903
  } catch {
551
- const locale = props.locale === "fr" ? "fr" : "en";
552
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
904
+ return void 0;
553
905
  }
554
- const isVirtual = props.rows.length > validatedThreshold;
555
- const resolvedLocale = props.locale === "fr" ? "fr" : "en";
556
- if (isVirtual) {
557
- return /* @__PURE__ */ jsxRuntime.jsx(
558
- VirtualTable,
559
- {
560
- rows: props.rows,
561
- columns: props.columns,
562
- ariaLabel: props.ariaLabel,
563
- locale: resolvedLocale,
564
- onRowClick: props.onRowClick
565
- }
566
- );
906
+ }
907
+ function TableView(props) {
908
+ const threshold = validate(props);
909
+ if (threshold === void 0) {
910
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
567
911
  }
568
912
  return /* @__PURE__ */ jsxRuntime.jsx(
569
- StaticTable,
913
+ TableCore,
570
914
  {
915
+ ...props,
571
916
  rows: props.rows,
572
- columns: props.columns,
573
- ariaLabel: props.ariaLabel,
574
- locale: resolvedLocale,
575
- onRowClick: props.onRowClick
917
+ virtualizeThreshold: threshold,
918
+ locale: resolveLocale(props.locale)
576
919
  }
577
920
  );
578
921
  }
579
922
  function StreamingTableView(props) {
580
- let validatedThreshold = 100;
581
- try {
582
- const validated = validateTableViewProps({
583
- columns: props.columns.map(({ key, header }) => ({ key, header })),
584
- virtualizeThreshold: props.virtualizeThreshold,
585
- ariaLabel: props.ariaLabel,
586
- locale: props.locale
587
- });
588
- validatedThreshold = validated.virtualizeThreshold;
589
- } catch {
590
- const locale = props.locale === "fr" ? "fr" : "en";
591
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
923
+ const threshold = validate(props);
924
+ if (threshold === void 0) {
925
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
592
926
  }
593
- return /* @__PURE__ */ jsxRuntime.jsx(
594
- StreamingTableViewInner,
595
- {
596
- ...props,
597
- virtualizeThreshold: validatedThreshold,
598
- onRowClick: props.onRowClick
599
- }
600
- );
927
+ return /* @__PURE__ */ jsxRuntime.jsx(StreamingTableViewInner, { ...props, virtualizeThreshold: threshold });
601
928
  }
602
929
  function StreamingTableViewInner({
603
- columns,
604
930
  rows$,
605
- virtualizeThreshold = 100,
606
- ariaLabel,
607
- locale,
608
- onRowClick
931
+ ...rest
609
932
  }) {
610
- const resolvedLocale = locale === "fr" ? "fr" : "en";
611
933
  const [rows, setRows] = react.useState([]);
612
934
  react.useEffect(() => {
613
935
  setRows([]);
@@ -618,87 +940,282 @@ function StreamingTableViewInner({
618
940
  sub.unsubscribe();
619
941
  };
620
942
  }, [rows$]);
621
- const isVirtual = rows.length > virtualizeThreshold;
622
- if (isVirtual) {
943
+ return /* @__PURE__ */ jsxRuntime.jsx(TableCore, { ...rest, rows, locale: resolveLocale(rest.locale) });
944
+ }
945
+ function kindOf(col) {
946
+ return col.type ?? "text";
947
+ }
948
+ function alignOf(col) {
949
+ return col.align ?? (col.type === "amount" || col.type === "number" ? "end" : "start");
950
+ }
951
+ function cellText(row, key) {
952
+ return String(row[key] ?? "");
953
+ }
954
+ function safeIconSrc(v) {
955
+ if (typeof v !== "string") return void 0;
956
+ return /^(https?:\/\/|data:image\/)/i.test(v) ? v : void 0;
957
+ }
958
+ function renderCell(col, row) {
959
+ if (col.render) {
960
+ return col.render(row);
961
+ }
962
+ const value = row[col.key];
963
+ if (col.type === "icon") {
964
+ const src = safeIconSrc(value);
965
+ return src ? /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "", width: 20, height: 20, style: { verticalAlign: "middle" } }) : null;
966
+ }
967
+ if (col.type === "amount" || col.type === "number") {
968
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: String(value ?? "") });
969
+ }
970
+ return String(value ?? "");
971
+ }
972
+ function TableCore(props) {
973
+ const {
974
+ columns,
975
+ rows,
976
+ ariaLabel,
977
+ locale,
978
+ selectionMode = "none",
979
+ stickyHeader = false,
980
+ density = "comfortable"
981
+ } = props;
982
+ const [innerSort, setInnerSort] = react.useState(props.defaultSort);
983
+ const sort = props.sort === void 0 ? innerSort : props.sort ?? void 0;
984
+ const onSort = (key) => {
985
+ const next = nextSort(sort, key);
986
+ if (props.sort === void 0) setInnerSort(next);
987
+ props.onSortChange?.(next);
988
+ };
989
+ const items = react.useMemo(
990
+ () => rows.map((row, i) => {
991
+ const id = row.id;
992
+ return { row, key: id != null ? String(id) : `row-${i}` };
993
+ }),
994
+ [rows]
995
+ );
996
+ const sorted = react.useMemo(() => {
997
+ const col = sort ? columns.find((c) => c.key === sort.key) : void 0;
998
+ if (!sort || !col) return items;
999
+ return sortItems(
1000
+ items,
1001
+ (it) => it.row[col.key],
1002
+ kindOf(col),
1003
+ sort.direction,
1004
+ locale
1005
+ );
1006
+ }, [items, sort, columns, locale]);
1007
+ const [innerSelected, setInnerSelected] = react.useState(
1008
+ () => new Set(props.defaultSelectedKeys ?? [])
1009
+ );
1010
+ const selected = props.selectedKeys ? new Set(props.selectedKeys) : innerSelected;
1011
+ const commit = (next) => {
1012
+ if (!props.selectedKeys) setInnerSelected(next);
1013
+ const chosen = sorted.filter((it) => next.has(it.key));
1014
+ props.onSelectionChange?.(
1015
+ chosen.map((it) => it.row),
1016
+ chosen.map((it) => it.key)
1017
+ );
1018
+ };
1019
+ const selection = selectionMode === "none" ? void 0 : {
1020
+ mode: selectionMode,
1021
+ selected,
1022
+ toggle: (key) => commit(toggleSelection(selected, key, selectionMode)),
1023
+ toggleAll: () => commit(
1024
+ toggleAll(
1025
+ selected,
1026
+ sorted.map((it) => it.key)
1027
+ )
1028
+ )
1029
+ };
1030
+ if (props.loading) {
1031
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("TableView.loading", locale), lang: locale, children: SKELETON_ROWS.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1032
+ Skeleton,
1033
+ {
1034
+ variant: "rect",
1035
+ height: i === 0 ? 24 : 36,
1036
+ locale
1037
+ },
1038
+ SKELETON_ROWS[i]
1039
+ )) });
1040
+ }
1041
+ const content = {
1042
+ items: sorted,
1043
+ columns,
1044
+ ariaLabel,
1045
+ locale,
1046
+ onRowClick: props.onRowClick,
1047
+ sort,
1048
+ onSort,
1049
+ selection,
1050
+ sticky: stickyHeader,
1051
+ density
1052
+ };
1053
+ const isVirtual = rows.length > props.virtualizeThreshold;
1054
+ if (isVirtual) return /* @__PURE__ */ jsxRuntime.jsx(VirtualTable, { ...content });
1055
+ if (stickyHeader) {
623
1056
  return /* @__PURE__ */ jsxRuntime.jsx(
624
- VirtualTable,
1057
+ "div",
625
1058
  {
626
- rows,
627
- columns,
628
- ariaLabel,
629
- locale: resolvedLocale,
630
- onRowClick
1059
+ tabIndex: 0,
1060
+ role: "region",
1061
+ "aria-label": ariaLabel,
1062
+ style: { maxHeight: props.maxHeight ?? 400, overflow: "auto" },
1063
+ children: /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content })
631
1064
  }
632
1065
  );
633
1066
  }
1067
+ return /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content });
1068
+ }
1069
+ var CELL_PADDING = { comfortable: void 0, compact: "2px 6px" };
1070
+ function HeaderRow({
1071
+ columns,
1072
+ locale,
1073
+ sort,
1074
+ onSort,
1075
+ selection,
1076
+ sticky,
1077
+ density,
1078
+ items
1079
+ }) {
1080
+ const padding = CELL_PADDING[density];
1081
+ const stickyStyle = sticky ? { position: "sticky", top: 0, background: "var(--mosaic-color-background, Canvas)" } : {};
1082
+ const allSelected = items.length > 0 && items.every((it) => selection?.selected.has(it.key));
1083
+ const someSelected = items.some((it) => selection?.selected.has(it.key));
1084
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1085
+ selection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { ...stickyStyle, padding, width: 32 }, children: selection.mode === "multiple" ? /* @__PURE__ */ jsxRuntime.jsx(
1086
+ "input",
1087
+ {
1088
+ type: "checkbox",
1089
+ "aria-label": t("TableView.select.all", locale),
1090
+ checked: allSelected,
1091
+ ref: (el) => {
1092
+ if (el) el.indeterminate = someSelected && !allSelected;
1093
+ },
1094
+ onChange: selection.toggleAll
1095
+ }
1096
+ ) : null }) : null,
1097
+ columns.map((col) => {
1098
+ const dir = sort?.key === col.key ? sort.direction : void 0;
1099
+ return /* @__PURE__ */ jsxRuntime.jsx(
1100
+ "th",
1101
+ {
1102
+ scope: "col",
1103
+ "aria-sort": col.sortable ? dir === "asc" ? "ascending" : dir === "desc" ? "descending" : "none" : void 0,
1104
+ style: {
1105
+ ...stickyStyle,
1106
+ padding,
1107
+ textAlign: alignOf(col),
1108
+ ...col.width !== void 0 ? { width: col.width } : {}
1109
+ },
1110
+ children: col.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
1111
+ "button",
1112
+ {
1113
+ type: "button",
1114
+ "aria-label": `${t("TableView.sort.by", locale)} ${col.header}`,
1115
+ onClick: () => onSort(col.key),
1116
+ style: { all: "inherit", cursor: "pointer", padding: 0 },
1117
+ children: [
1118
+ col.header,
1119
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: dir === "asc" ? " \u25B2" : dir === "desc" ? " \u25BC" : "" })
1120
+ ]
1121
+ }
1122
+ ) : col.header
1123
+ },
1124
+ col.key
1125
+ );
1126
+ })
1127
+ ] });
1128
+ }
1129
+ function SelectCell({
1130
+ item,
1131
+ columns,
1132
+ selection,
1133
+ locale,
1134
+ padding
1135
+ }) {
1136
+ const first = columns[0];
1137
+ const label = `${t("TableView.select.row", locale)}: ${first ? cellText(item.row, first.key) : item.key}`;
634
1138
  return /* @__PURE__ */ jsxRuntime.jsx(
635
- StaticTable,
1139
+ "td",
636
1140
  {
637
- rows,
638
- columns,
639
- ariaLabel,
640
- locale: resolvedLocale,
641
- onRowClick
1141
+ style: { padding, width: 32 },
1142
+ onClick: (e) => e.stopPropagation(),
1143
+ onKeyDown: (e) => e.stopPropagation(),
1144
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1145
+ "input",
1146
+ {
1147
+ type: selection.mode === "multiple" ? "checkbox" : "radio",
1148
+ name: selection.mode === "single" ? "mosaic-table-selection" : void 0,
1149
+ "aria-label": label,
1150
+ checked: selection.selected.has(item.key),
1151
+ onChange: () => selection.toggle(item.key),
1152
+ onKeyDown: (e) => e.stopPropagation()
1153
+ }
1154
+ )
642
1155
  }
643
1156
  );
644
1157
  }
645
- function renderCell(col, row) {
646
- if (col.render) {
647
- return col.render(row);
648
- }
649
- return String(row[col.key] ?? "");
1158
+ function rowActivation(onRowClick, row, index) {
1159
+ if (!onRowClick) return {};
1160
+ return {
1161
+ role: "button",
1162
+ tabIndex: 0,
1163
+ onClick: () => onRowClick(row, index),
1164
+ onKeyDown: (e) => {
1165
+ if (e.key === "Enter" || e.key === " ") {
1166
+ e.preventDefault();
1167
+ onRowClick(row, index);
1168
+ }
1169
+ }
1170
+ };
650
1171
  }
651
- function StaticTable({
652
- rows,
653
- columns,
654
- ariaLabel,
655
- locale,
656
- onRowClick
657
- }) {
1172
+ function StaticTable(props) {
1173
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
658
1174
  const emptyMsg = t("TableView.empty.message", locale);
1175
+ const padding = CELL_PADDING[density];
1176
+ const colSpan = columns.length + (selection ? 1 : 0);
659
1177
  return /* @__PURE__ */ jsxRuntime.jsxs(
660
1178
  "table",
661
1179
  {
662
1180
  "aria-label": ariaLabel,
663
- "aria-rowcount": rows.length,
1181
+ "aria-rowcount": items.length,
1182
+ "data-density": density,
664
1183
  style: { width: "100%", borderCollapse: "collapse" },
665
1184
  children: [
666
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
667
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: columns.length, "aria-label": emptyMsg, children: emptyMsg }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1185
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props }) }),
1186
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan, "aria-label": emptyMsg, children: emptyMsg }) }) : items.map((item, rowIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
668
1187
  "tr",
669
1188
  {
670
1189
  "aria-rowindex": rowIndex + 2,
671
- ...onRowClick ? {
672
- role: "button",
673
- tabIndex: 0,
674
- style: { cursor: "pointer" },
675
- onClick: () => onRowClick(row, rowIndex),
676
- onKeyDown: (e) => {
677
- if (e.key === "Enter" || e.key === " ") {
678
- e.preventDefault();
679
- onRowClick(row, rowIndex);
1190
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
1191
+ ...onRowClick ? { style: { cursor: "pointer" } } : {},
1192
+ ...rowActivation(onRowClick, item.row, rowIndex),
1193
+ children: [
1194
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
1195
+ SelectCell,
1196
+ {
1197
+ item,
1198
+ columns,
1199
+ selection,
1200
+ locale,
1201
+ padding
680
1202
  }
681
- }
682
- } : {},
683
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(col, row) }, col.key))
1203
+ ) : null,
1204
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1205
+ ]
684
1206
  },
685
- row.id != null ? String(row.id) : `row-${rowIndex}`
1207
+ item.key
686
1208
  )) })
687
1209
  ]
688
1210
  }
689
1211
  );
690
1212
  }
691
- function VirtualTable({
692
- rows,
693
- columns,
694
- ariaLabel,
695
- // locale reserved for future virtual-mode empty/pagination strings
696
- locale: _locale,
697
- onRowClick
698
- }) {
1213
+ function VirtualTable(props) {
1214
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
699
1215
  const scrollRef = react.useRef(null);
1216
+ const padding = CELL_PADDING[density];
700
1217
  const rowVirtualizer = reactVirtual.useVirtualizer({
701
- count: rows.length,
1218
+ count: items.length,
702
1219
  /* v8 ignore next 1 */
703
1220
  getScrollElement: () => scrollRef.current,
704
1221
  estimateSize: react.useCallback(() => 40, []),
@@ -711,18 +1228,19 @@ function VirtualTable({
711
1228
  {
712
1229
  "data-virtual": "true",
713
1230
  ref: scrollRef,
714
- tabIndex: 0,
715
1231
  role: "region",
1232
+ tabIndex: 0,
716
1233
  "aria-label": ariaLabel,
717
1234
  style: { height: 400, overflow: "auto", position: "relative" },
718
1235
  children: /* @__PURE__ */ jsxRuntime.jsxs(
719
1236
  "table",
720
1237
  {
721
1238
  "aria-label": ariaLabel,
722
- "aria-rowcount": rows.length,
1239
+ "aria-rowcount": items.length,
1240
+ "data-density": density,
723
1241
  style: { width: "100%", borderCollapse: "collapse" },
724
1242
  children: [
725
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
1243
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props, sticky: true }) }),
726
1244
  /* @__PURE__ */ jsxRuntime.jsx(
727
1245
  "tbody",
728
1246
  {
@@ -732,11 +1250,12 @@ function VirtualTable({
732
1250
  display: "block"
733
1251
  },
734
1252
  children: virtualItems.map((virtualRow) => {
735
- const row = rows[virtualRow.index] ?? {};
736
- return /* @__PURE__ */ jsxRuntime.jsx(
1253
+ const item = items[virtualRow.index] ?? { row: {}, key: "" };
1254
+ return /* @__PURE__ */ jsxRuntime.jsxs(
737
1255
  "tr",
738
1256
  {
739
1257
  "aria-rowindex": virtualRow.index + 2,
1258
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
740
1259
  style: {
741
1260
  position: "absolute",
742
1261
  top: 0,
@@ -746,20 +1265,22 @@ function VirtualTable({
746
1265
  display: "flex",
747
1266
  ...onRowClick ? { cursor: "pointer" } : {}
748
1267
  },
749
- ...onRowClick ? {
750
- role: "button",
751
- tabIndex: 0,
752
- onClick: () => onRowClick(row, virtualRow.index),
753
- onKeyDown: (e) => {
754
- if (e.key === "Enter" || e.key === " ") {
755
- e.preventDefault();
756
- onRowClick(row, virtualRow.index);
1268
+ ...rowActivation(onRowClick, item.row, virtualRow.index),
1269
+ children: [
1270
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
1271
+ SelectCell,
1272
+ {
1273
+ item,
1274
+ columns,
1275
+ selection,
1276
+ locale,
1277
+ padding
757
1278
  }
758
- }
759
- } : {},
760
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1 }, children: renderCell(col, row) }, col.key))
1279
+ ) : null,
1280
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1, padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1281
+ ]
761
1282
  },
762
- row.id != null ? String(row.id) : `vrow-${virtualRow.index}`
1283
+ item.key || `vrow-${virtualRow.index}`
763
1284
  );
764
1285
  })
765
1286
  }
@@ -770,11 +1291,496 @@ function VirtualTable({
770
1291
  }
771
1292
  );
772
1293
  }
1294
+ var StatCardPropsSchema = zod.z.object({
1295
+ label: zod.z.string().default(""),
1296
+ value: zod.z.string().default(""),
1297
+ unit: zod.z.string().optional(),
1298
+ trend: zod.z.enum(["up", "down", "flat"]).optional(),
1299
+ /** Pre-formatted change, e.g. "+4.2%". */
1300
+ change: zod.z.string().optional(),
1301
+ tone: zod.z.enum(["neutral", "success", "warning", "danger"]).default("neutral"),
1302
+ description: zod.z.string().optional(),
1303
+ loading: zod.z.boolean().default(false),
1304
+ locale: zod.z.enum(["en", "fr"]).default("en")
1305
+ });
1306
+ function validateStatCardProps(raw) {
1307
+ return StatCardPropsSchema.parse(raw);
1308
+ }
1309
+ var TONE_CLASSES = {
1310
+ neutral: "text-slate-900",
1311
+ success: "text-green-700",
1312
+ warning: "text-amber-700",
1313
+ danger: "text-red-700"
1314
+ };
1315
+ var TREND_ARROW = { up: "\u25B2", down: "\u25BC", flat: "\u25AC" };
1316
+ function StatCard(raw) {
1317
+ const parsed = StatCardPropsSchema.safeParse(raw);
1318
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1319
+ if (!parsed.success) {
1320
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("StatCard.error.invalidProps", locale) });
1321
+ }
1322
+ const p = parsed.data;
1323
+ if (p.loading) {
1324
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("StatCard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 72, locale: p.locale }) });
1325
+ }
1326
+ if (p.label === "" && p.value === "") {
1327
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("StatCard.empty.message", p.locale), locale: p.locale });
1328
+ }
1329
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1330
+ "section",
1331
+ {
1332
+ "aria-label": `${t("StatCard.title", p.locale)}: ${p.label}`,
1333
+ lang: p.locale,
1334
+ className: "flex flex-col gap-1 rounded-lg border border-slate-200 bg-white p-4",
1335
+ children: [
1336
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.label }),
1337
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-2xl font-semibold tabular-nums ${TONE_CLASSES[p.tone]}`, children: [
1338
+ p.value,
1339
+ p.unit ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-base font-normal text-slate-500", children: p.unit }) : null
1340
+ ] }),
1341
+ p.trend || p.change ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-sm text-slate-600", children: [
1342
+ p.trend ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1343
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: TREND_ARROW[p.trend] }),
1344
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`StatCard.trend.${p.trend}`, p.locale) })
1345
+ ] }) : null,
1346
+ p.change ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: p.change }) : null
1347
+ ] }) : null,
1348
+ p.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.description }) : null
1349
+ ]
1350
+ }
1351
+ );
1352
+ }
1353
+
1354
+ // src/components/shared/decimal.ts
1355
+ var DECIMAL2 = /^(-)?(\d+)(?:\.(\d+))?$/;
1356
+ function sumDecimalStrings(values) {
1357
+ let scale = 0;
1358
+ const parsed = [];
1359
+ for (const v of values) {
1360
+ const m = DECIMAL2.exec(v);
1361
+ if (!m) return void 0;
1362
+ const frac2 = m[3] ?? "";
1363
+ scale = Math.max(scale, frac2.length);
1364
+ parsed.push({ neg: m[1] === "-", int: m[2] ?? "0", frac: frac2 });
1365
+ }
1366
+ let total = 0n;
1367
+ for (const p of parsed) {
1368
+ const n = BigInt(p.int + p.frac.padEnd(scale, "0"));
1369
+ total += p.neg ? -n : n;
1370
+ }
1371
+ const neg = total < 0n;
1372
+ const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
1373
+ const int = digits.slice(0, digits.length - scale);
1374
+ const frac = digits.slice(digits.length - scale).replace(/0+$/, "");
1375
+ return `${neg ? "-" : ""}${int}${frac ? `.${frac}` : ""}`;
1376
+ }
1377
+ function formatDecimalString(value, locale, currency) {
1378
+ if (!DECIMAL2.test(value)) return value;
1379
+ try {
1380
+ const nf = new Intl.NumberFormat(
1381
+ locale,
1382
+ currency ? { style: "currency", currency } : { maximumFractionDigits: 20 }
1383
+ );
1384
+ return nf.format(value);
1385
+ } catch {
1386
+ return value;
1387
+ }
1388
+ }
1389
+ function truncateAddress(address, head = 6, tail = 4) {
1390
+ return address.length <= head + tail + 1 ? address : `${address.slice(0, head)}\u2026${address.slice(address.length - tail)}`;
1391
+ }
1392
+ var BalanceCardPropsSchema = zod.z.object({
1393
+ token: zod.z.string().default(""),
1394
+ balance: zod.z.string().default(""),
1395
+ address: zod.z.string().default(""),
1396
+ tokenAddress: zod.z.string().optional(),
1397
+ decimals: zod.z.number().int().nonnegative().optional(),
1398
+ network: zod.z.string().optional(),
1399
+ testnet: zod.z.boolean().default(false),
1400
+ /** Decimal string in `currency`. */
1401
+ usdValue: zod.z.string().optional(),
1402
+ currency: zod.z.string().default("USD"),
1403
+ loading: zod.z.boolean().default(false),
1404
+ locale: zod.z.enum(["en", "fr"]).default("en")
1405
+ });
1406
+ function validateBalanceCardProps(raw) {
1407
+ return BalanceCardPropsSchema.parse(raw);
1408
+ }
1409
+ function BalanceCard(raw) {
1410
+ const parsed = BalanceCardPropsSchema.safeParse(raw);
1411
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1412
+ const [copied, setCopied] = react.useState(false);
1413
+ if (!parsed.success) {
1414
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("BalanceCard.error.invalidProps", locale) });
1415
+ }
1416
+ const p = parsed.data;
1417
+ if (p.loading) {
1418
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("BalanceCard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 120, locale: p.locale }) });
1419
+ }
1420
+ if (p.token === "" && p.balance === "") {
1421
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("BalanceCard.empty.message", p.locale), locale: p.locale });
1422
+ }
1423
+ const canCopy = typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
1424
+ const copy = async () => {
1425
+ try {
1426
+ await navigator.clipboard.writeText(p.address);
1427
+ setCopied(true);
1428
+ } catch {
1429
+ }
1430
+ };
1431
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1432
+ "section",
1433
+ {
1434
+ "aria-label": `${t("BalanceCard.title", p.locale)}: ${p.token}`,
1435
+ lang: p.locale,
1436
+ className: "flex flex-col gap-2",
1437
+ children: [
1438
+ /* @__PURE__ */ jsxRuntime.jsx(
1439
+ StatCard,
1440
+ {
1441
+ label: t("BalanceCard.title", p.locale),
1442
+ value: p.balance,
1443
+ unit: p.token,
1444
+ locale: p.locale
1445
+ }
1446
+ ),
1447
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1448
+ p.network ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: p.network, variant: "info", locale: p.locale }) : null,
1449
+ p.testnet ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: t("BalanceCard.testnet", p.locale), variant: "warning", locale: p.locale }) : null
1450
+ ] }),
1451
+ p.usdValue ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-slate-600", children: [
1452
+ t("BalanceCard.value", p.locale),
1453
+ ":",
1454
+ " ",
1455
+ formatDecimalString(p.usdValue, p.locale, p.currency)
1456
+ ] }) : null,
1457
+ p.address ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-slate-600", children: [
1458
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", title: p.address, children: truncateAddress(p.address) }),
1459
+ canCopy ? /* @__PURE__ */ jsxRuntime.jsx(
1460
+ "button",
1461
+ {
1462
+ type: "button",
1463
+ onClick: copy,
1464
+ className: "rounded border border-slate-300 px-2 py-0.5 text-xs hover:bg-slate-50",
1465
+ children: t("BalanceCard.copy", p.locale)
1466
+ }
1467
+ ) : null,
1468
+ copied ? /* @__PURE__ */ jsxRuntime.jsx("output", { children: t("BalanceCard.copied", p.locale) }) : null
1469
+ ] }) : null
1470
+ ]
1471
+ }
1472
+ );
1473
+ }
1474
+
1475
+ // src/components/shared/datetime.ts
1476
+ function formatTimestamp(iso, locale, timeZone) {
1477
+ const d = new Date(iso);
1478
+ if (Number.isNaN(d.getTime())) return iso;
1479
+ try {
1480
+ return new Intl.DateTimeFormat(locale, {
1481
+ dateStyle: "medium",
1482
+ timeStyle: "short",
1483
+ ...timeZone ? { timeZone } : {}
1484
+ }).format(d);
1485
+ } catch {
1486
+ return iso;
1487
+ }
1488
+ }
1489
+
1490
+ // src/components/shared/url.ts
1491
+ function normalizeHttpUrl(value) {
1492
+ if (typeof value !== "string") return void 0;
1493
+ try {
1494
+ const u = new URL(value);
1495
+ return u.protocol === "https:" || u.protocol === "http:" ? u.href : void 0;
1496
+ } catch {
1497
+ return void 0;
1498
+ }
1499
+ }
1500
+ var FeedMessageSchema = zod.z.object({
1501
+ id: zod.z.string().optional(),
1502
+ sender: zod.z.string().min(1),
1503
+ channel: zod.z.string().optional(),
1504
+ /** Plain text. Rendered as text, never as HTML. */
1505
+ content: zod.z.string(),
1506
+ /** ISO 8601. */
1507
+ timestamp: zod.z.string(),
1508
+ attachmentUrl: zod.z.string().optional(),
1509
+ attachmentLabel: zod.z.string().optional(),
1510
+ unread: zod.z.boolean().optional()
1511
+ });
1512
+ var MessageFeedPropsSchema = zod.z.object({
1513
+ messages: zod.z.array(FeedMessageSchema).default([]),
1514
+ maxItems: zod.z.number().int().positive().optional(),
1515
+ /** IANA timezone for timestamps (e.g. `hostContext.timeZone`). */
1516
+ timeZone: zod.z.string().optional(),
1517
+ /** Above this many shown messages the feed is virtualised through VirtualList. */
1518
+ virtualizeThreshold: zod.z.number().int().positive().default(50),
1519
+ loading: zod.z.boolean().default(false),
1520
+ locale: zod.z.enum(["en", "fr"]).default("en")
1521
+ });
1522
+ function validateMessageFeedProps(raw) {
1523
+ return MessageFeedPropsSchema.parse(raw);
1524
+ }
1525
+ function latestMessages(messages, maxItems) {
1526
+ const shown = maxItems === void 0 ? [...messages] : messages.slice(-maxItems);
1527
+ return { shown, total: messages.length };
1528
+ }
1529
+ function Attachment({
1530
+ m,
1531
+ locale,
1532
+ onOpenLink
1533
+ }) {
1534
+ if (!m.attachmentUrl) return null;
1535
+ const label = m.attachmentLabel ?? t("MessageFeed.attachment", locale);
1536
+ const url = normalizeHttpUrl(m.attachmentUrl);
1537
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: label });
1538
+ return onOpenLink ? /* @__PURE__ */ jsxRuntime.jsx(
1539
+ "button",
1540
+ {
1541
+ type: "button",
1542
+ onClick: () => onOpenLink(url),
1543
+ className: "text-left text-sm text-blue-700 underline",
1544
+ children: label
1545
+ }
1546
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1547
+ "a",
1548
+ {
1549
+ href: url,
1550
+ rel: "noopener noreferrer",
1551
+ target: "_blank",
1552
+ className: "text-sm text-blue-700 underline",
1553
+ children: label
1554
+ }
1555
+ );
1556
+ }
1557
+ function Message({
1558
+ m,
1559
+ locale,
1560
+ timeZone,
1561
+ onOpenLink
1562
+ }) {
1563
+ return /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "flex flex-col gap-1 border-b border-slate-100 py-2", children: [
1564
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
1565
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: m.sender }),
1566
+ m.channel ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: m.channel, variant: "neutral", locale }) : null,
1567
+ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: m.timestamp, className: "text-xs text-slate-500", children: formatTimestamp(m.timestamp, locale, timeZone) }),
1568
+ m.unread ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-blue-700", children: t("MessageFeed.unread", locale) }) : null
1569
+ ] }),
1570
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 whitespace-pre-wrap text-sm text-slate-800", children: m.content }),
1571
+ /* @__PURE__ */ jsxRuntime.jsx(Attachment, { m, locale, onOpenLink })
1572
+ ] });
1573
+ }
1574
+ function MessageFeed(raw = {}) {
1575
+ const parsed = MessageFeedPropsSchema.safeParse(raw);
1576
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1577
+ if (!parsed.success) {
1578
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("MessageFeed.error.invalidProps", locale) });
1579
+ }
1580
+ const p = parsed.data;
1581
+ if (p.loading) {
1582
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("MessageFeed.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", count: 5, locale: p.locale }) });
1583
+ }
1584
+ if (p.messages.length === 0) {
1585
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("MessageFeed.empty.message", p.locale), locale: p.locale });
1586
+ }
1587
+ const { shown, total } = latestMessages(p.messages, p.maxItems);
1588
+ const virtual = shown.length > p.virtualizeThreshold;
1589
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": t("MessageFeed.title", p.locale), lang: p.locale, children: [
1590
+ shown.length < total ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 pb-1 text-xs text-slate-500", children: t("MessageFeed.showing", p.locale).replace("{n}", String(shown.length)).replace("{total}", String(total)) }) : null,
1591
+ virtual ? /* @__PURE__ */ jsxRuntime.jsx(
1592
+ VirtualList,
1593
+ {
1594
+ items: shown,
1595
+ itemHeight: 96,
1596
+ locale: p.locale,
1597
+ renderItem: (m) => /* @__PURE__ */ jsxRuntime.jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink })
1598
+ }
1599
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "m-0 list-none p-0", children: shown.map((m, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink }) }, m.id ?? `msg-${i}`)) })
1600
+ ] });
1601
+ }
1602
+
1603
+ // src/components/forms/RadioGroup.logic.ts
1604
+ function getOptionLabelClasses(appearance, isSelected) {
1605
+ const base = "mosaic-forms-radiogroup-option-label inline-flex items-center px-3 py-1 text-sm";
1606
+ const shape = "rounded-full border" ;
1607
+ const tone = isSelected ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-700 border-slate-300 hover:bg-slate-50";
1608
+ return [base, shape, tone].filter(Boolean).join(" ");
1609
+ }
1610
+
1611
+ // src/components/display/PipelineBoard.logic.ts
1612
+ function stageSummary(deals) {
1613
+ const amounts = deals.flatMap((d) => d.amount === void 0 ? [] : [d.amount]);
1614
+ return { count: deals.length, total: sumDecimalStrings(amounts) };
1615
+ }
1616
+ var SUMMARY_STAGE_LIMIT = 5;
1617
+ var PipelineDealSchema = zod.z.object({
1618
+ id: zod.z.string().min(1),
1619
+ title: zod.z.string().min(1),
1620
+ /** Decimal STRING (never a float), in the board currency. */
1621
+ amount: zod.z.string().optional(),
1622
+ contact: zod.z.string().optional(),
1623
+ url: zod.z.string().optional()
1624
+ });
1625
+ var PipelineStageSchema = zod.z.object({
1626
+ id: zod.z.string().min(1),
1627
+ name: zod.z.string().min(1),
1628
+ deals: zod.z.array(PipelineDealSchema).default([])
1629
+ });
1630
+ var PipelineBoardPropsSchema = zod.z.object({
1631
+ stages: zod.z.array(PipelineStageSchema).default([]),
1632
+ /** ISO 4217 code used to format amounts. */
1633
+ currency: zod.z.string().default("EUR"),
1634
+ layout: zod.z.enum(["board", "summary"]).default("board"),
1635
+ loading: zod.z.boolean().default(false),
1636
+ locale: zod.z.enum(["en", "fr"]).default("en")
1637
+ });
1638
+ function validatePipelineBoardProps(raw) {
1639
+ return PipelineBoardPropsSchema.parse(raw);
1640
+ }
1641
+ function dealsLabel(n, locale) {
1642
+ return n === 1 ? t("PipelineBoard.deals.one", locale) : t("PipelineBoard.deals.many", locale).replace("{n}", String(n));
1643
+ }
1644
+ function Totals({
1645
+ stage,
1646
+ currency,
1647
+ locale
1648
+ }) {
1649
+ const { count, total } = stageSummary(stage.deals);
1650
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col text-sm text-slate-600", children: [
1651
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: dealsLabel(count, locale) }),
1652
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: total === void 0 ? "\u2014" : formatDecimalString(total, locale, currency) })
1653
+ ] });
1654
+ }
1655
+ function PipelineBoard(raw = {}) {
1656
+ const parsed = PipelineBoardPropsSchema.safeParse(raw);
1657
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1658
+ const [hidden, setHidden] = react.useState(/* @__PURE__ */ new Set());
1659
+ if (!parsed.success) {
1660
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("PipelineBoard.error.invalidProps", locale) });
1661
+ }
1662
+ const p = parsed.data;
1663
+ if (p.loading) {
1664
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("PipelineBoard.loading", p.locale), children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rect", height: 160, locale: p.locale }) });
1665
+ }
1666
+ if (p.stages.length === 0) {
1667
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("PipelineBoard.empty.message", p.locale), locale: p.locale });
1668
+ }
1669
+ if (p.layout === "summary") {
1670
+ const shown = p.stages.slice(0, SUMMARY_STAGE_LIMIT);
1671
+ const rest = p.stages.length - shown.length;
1672
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1673
+ "section",
1674
+ {
1675
+ "aria-label": t("PipelineBoard.title", p.locale),
1676
+ lang: p.locale,
1677
+ className: "flex flex-col gap-2",
1678
+ children: [
1679
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: shown.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-2", children: [
1680
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: s.name }),
1681
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1682
+ ] }, s.id)) }),
1683
+ rest > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.more", p.locale).replace("{n}", String(rest)) }) : null,
1684
+ raw.onRequestFullscreen ? /* @__PURE__ */ jsxRuntime.jsx(
1685
+ "button",
1686
+ {
1687
+ type: "button",
1688
+ onClick: raw.onRequestFullscreen,
1689
+ className: "self-start rounded border border-slate-300 px-3 py-1 text-sm hover:bg-slate-50",
1690
+ children: t("PipelineBoard.summary.open", p.locale)
1691
+ }
1692
+ ) : null
1693
+ ]
1694
+ }
1695
+ );
1696
+ }
1697
+ const visible = p.stages.filter((s) => !hidden.has(s.id));
1698
+ const toggle = (id) => {
1699
+ const next = new Set(hidden);
1700
+ if (next.has(id)) next.delete(id);
1701
+ else if (visible.length > 1) next.add(id);
1702
+ setHidden(next);
1703
+ };
1704
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1705
+ "section",
1706
+ {
1707
+ "aria-label": t("PipelineBoard.title", p.locale),
1708
+ lang: p.locale,
1709
+ className: "flex flex-col gap-3",
1710
+ children: [
1711
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "m-0 flex min-w-0 flex-wrap gap-2 border-0 p-0", children: [
1712
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "sr-only", children: t("PipelineBoard.filter.label", p.locale) }),
1713
+ p.stages.map((s) => {
1714
+ const on = !hidden.has(s.id);
1715
+ return /* @__PURE__ */ jsxRuntime.jsx(
1716
+ "button",
1717
+ {
1718
+ type: "button",
1719
+ "aria-pressed": on,
1720
+ onClick: () => toggle(s.id),
1721
+ className: getOptionLabelClasses("chips", on),
1722
+ children: s.name
1723
+ },
1724
+ s.id
1725
+ );
1726
+ })
1727
+ ] }),
1728
+ /* @__PURE__ */ jsxRuntime.jsx(
1729
+ "div",
1730
+ {
1731
+ className: "flex flex-row gap-3 overflow-x-auto",
1732
+ style: {
1733
+ scrollPaddingLeft: "var(--mosaic-safe-area-left, 0px)",
1734
+ scrollPaddingRight: "var(--mosaic-safe-area-right, 0px)"
1735
+ },
1736
+ children: visible.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
1737
+ "section",
1738
+ {
1739
+ "aria-label": s.name,
1740
+ className: "flex w-64 shrink-0 flex-col gap-2 rounded-lg border border-slate-200 bg-slate-50 p-3",
1741
+ children: [
1742
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start justify-between gap-2", children: [
1743
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: s.name }),
1744
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1745
+ ] }),
1746
+ s.deals.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.stage.empty", p.locale) }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: s.deals.map((d) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "rounded border border-slate-200 bg-white p-2", children: [
1747
+ raw.onOpenLink && normalizeHttpUrl(d.url) ? /* @__PURE__ */ jsxRuntime.jsx(
1748
+ "button",
1749
+ {
1750
+ type: "button",
1751
+ onClick: () => raw.onOpenLink?.(normalizeHttpUrl(d.url)),
1752
+ className: "text-left font-medium text-blue-700 underline",
1753
+ children: d.title
1754
+ }
1755
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: d.title }),
1756
+ d.amount !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm tabular-nums text-slate-700", children: formatDecimalString(d.amount, p.locale, p.currency) }) : null,
1757
+ d.contact ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: d.contact, variant: "neutral", locale: p.locale }) : null
1758
+ ] }, d.id)) })
1759
+ ]
1760
+ },
1761
+ s.id
1762
+ ))
1763
+ }
1764
+ )
1765
+ ]
1766
+ }
1767
+ );
1768
+ }
773
1769
 
1770
+ exports.Badge = Badge;
1771
+ exports.BadgePropsSchema = BadgePropsSchema;
1772
+ exports.BalanceCard = BalanceCard;
1773
+ exports.BalanceCardPropsSchema = BalanceCardPropsSchema;
774
1774
  exports.EmptyState = EmptyState;
775
1775
  exports.EmptyStatePropsSchema = EmptyStatePropsSchema;
1776
+ exports.MessageFeed = MessageFeed;
1777
+ exports.MessageFeedPropsSchema = MessageFeedPropsSchema;
1778
+ exports.PipelineBoard = PipelineBoard;
1779
+ exports.PipelineBoardPropsSchema = PipelineBoardPropsSchema;
776
1780
  exports.Skeleton = Skeleton;
777
1781
  exports.SkeletonPropsSchema = SkeletonPropsSchema;
1782
+ exports.StatCard = StatCard;
1783
+ exports.StatCardPropsSchema = StatCardPropsSchema;
778
1784
  exports.StatusBadge = StatusBadge;
779
1785
  exports.StatusBadgePropsSchema = StatusBadgePropsSchema;
780
1786
  exports.StreamingTableView = StreamingTableView;
@@ -782,9 +1788,17 @@ exports.TableView = TableView;
782
1788
  exports.TableViewPropsSchema = TableViewPropsSchema;
783
1789
  exports.VirtualList = VirtualList;
784
1790
  exports.VirtualListPropsSchema = VirtualListPropsSchema;
1791
+ exports.compareDecimalStrings = compareDecimalStrings;
1792
+ exports.nextSort = nextSort;
785
1793
  exports.resolveSkeletonDimensions = resolveSkeletonDimensions;
1794
+ exports.sortItems = sortItems;
1795
+ exports.validateBadgeProps = validateBadgeProps;
1796
+ exports.validateBalanceCardProps = validateBalanceCardProps;
786
1797
  exports.validateEmptyStateProps = validateEmptyStateProps;
1798
+ exports.validateMessageFeedProps = validateMessageFeedProps;
1799
+ exports.validatePipelineBoardProps = validatePipelineBoardProps;
787
1800
  exports.validateSkeletonProps = validateSkeletonProps;
1801
+ exports.validateStatCardProps = validateStatCardProps;
788
1802
  exports.validateStatusBadgeProps = validateProps;
789
1803
  exports.validateTableViewProps = validateTableViewProps;
790
1804
  exports.validateVirtualListProps = validateVirtualListProps;