@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
package/dist/display.cjs CHANGED
@@ -29,6 +29,8 @@ var EN = {
29
29
  "TokenDisplayOnceModal.copied": "Copied",
30
30
  "StatusBadge.aria.status": "Status",
31
31
  "StatusBadge.error.invalidProps": "StatusBadge: invalid props",
32
+ "Badge.aria.label": "Badge",
33
+ "Badge.error.invalidProps": "Badge: invalid props",
32
34
  "EmptyState.error.invalidProps": "EmptyState: invalid props",
33
35
  "Toast.aria.close": "Close notification",
34
36
  "Toast.error.invalidProps": "Toast: invalid props",
@@ -98,7 +100,99 @@ var EN = {
98
100
  "Select.search.placeholder": "Search\u2026",
99
101
  "Select.empty": "No options available",
100
102
  "Select.error.required": "Select an option",
101
- "Select.error.invalidProps": "Select: invalid props"
103
+ "Select.error.invalidProps": "Select: invalid props",
104
+ "ModelContext.selection.one": "1 row selected",
105
+ "ModelContext.selection.many": "{n} rows selected",
106
+ "ModelContext.selection.none": "No rows selected",
107
+ "TableView.sort.by": "Sort by",
108
+ "TableView.select.row": "Select row",
109
+ "TableView.select.all": "Select all rows",
110
+ "TableView.loading": "Loading table",
111
+ "StatCard.title": "Statistic",
112
+ "StatCard.trend.up": "Up",
113
+ "StatCard.trend.down": "Down",
114
+ "StatCard.trend.flat": "Flat",
115
+ "StatCard.empty.message": "No statistic to display",
116
+ "StatCard.loading": "Loading statistic",
117
+ "StatCard.error.invalidProps": "StatCard: invalid props",
118
+ "BalanceCard.title": "Balance",
119
+ "BalanceCard.network": "Network",
120
+ "BalanceCard.testnet": "Testnet",
121
+ "BalanceCard.address": "Address",
122
+ "BalanceCard.contract": "Token contract",
123
+ "BalanceCard.value": "Value",
124
+ "BalanceCard.copy": "Copy address",
125
+ "BalanceCard.copied": "Address copied",
126
+ "BalanceCard.empty.message": "No balance to display",
127
+ "BalanceCard.loading": "Loading balance",
128
+ "BalanceCard.error.invalidProps": "BalanceCard: invalid props",
129
+ "Timeline.title": "Timeline",
130
+ "Timeline.status.done": "Done",
131
+ "Timeline.status.current": "In progress",
132
+ "Timeline.status.pending": "Pending",
133
+ "Timeline.status.blocked": "Blocked",
134
+ "Timeline.completed": "{n} completed",
135
+ "Timeline.more": "{n} more",
136
+ "Timeline.empty.message": "No steps to display",
137
+ "Timeline.loading": "Loading timeline",
138
+ "Timeline.error.invalidProps": "Timeline: invalid props",
139
+ "MessageFeed.title": "Messages",
140
+ "MessageFeed.unread": "Unread",
141
+ "MessageFeed.attachment": "Attachment",
142
+ "MessageFeed.showing": "Showing the latest {n} of {total}",
143
+ "MessageFeed.empty.message": "No messages",
144
+ "MessageFeed.loading": "Loading messages",
145
+ "MessageFeed.error.invalidProps": "MessageFeed: invalid props",
146
+ "PipelineBoard.title": "Pipeline",
147
+ "PipelineBoard.deals.one": "1 deal",
148
+ "PipelineBoard.deals.many": "{n} deals",
149
+ "PipelineBoard.total": "Total",
150
+ "PipelineBoard.stage.empty": "No deals",
151
+ "PipelineBoard.filter.label": "Show stages",
152
+ "PipelineBoard.summary.open": "Open full board",
153
+ "PipelineBoard.more": "{n} more stages",
154
+ "PipelineBoard.empty.message": "No pipeline to display",
155
+ "PipelineBoard.loading": "Loading pipeline",
156
+ "PipelineBoard.error.invalidProps": "PipelineBoard: invalid props",
157
+ "PipelineBoard.col.stage": "Stage",
158
+ "PipelineBoard.col.deals": "Deals",
159
+ "TransactionPreview.title": "Transaction preview",
160
+ "TransactionPreview.notice": "NOT SIGNED - preparation only",
161
+ "TransactionPreview.kind.transfer": "Transfer",
162
+ "TransactionPreview.kind.swap": "Swap",
163
+ "TransactionPreview.testnet": "Testnet",
164
+ "TransactionPreview.field.network": "Network",
165
+ "TransactionPreview.field.from": "From",
166
+ "TransactionPreview.field.to": "To",
167
+ "TransactionPreview.field.amount": "Amount",
168
+ "TransactionPreview.field.fee": "Network fee",
169
+ "TransactionPreview.field.sell": "Sell",
170
+ "TransactionPreview.field.buy": "Buy",
171
+ "TransactionPreview.field.minReceived": "Minimum received",
172
+ "TransactionPreview.field.route": "Route",
173
+ "TransactionPreview.field.priceImpact": "Price impact",
174
+ "TransactionPreview.field.expires": "Quote expires",
175
+ "TransactionPreview.priceImpact.high": "High price impact",
176
+ "TransactionPreview.simulation.success": "Simulation succeeded",
177
+ "TransactionPreview.simulation.failed": "Simulation failed",
178
+ "TransactionPreview.simulation.unavailable": "Simulation unavailable",
179
+ "TransactionPreview.warnings": "Warnings",
180
+ "TransactionPreview.handoff": "Open in wallet",
181
+ "TransactionPreview.empty.message": "No transaction to preview",
182
+ "TransactionPreview.loading": "Loading preview",
183
+ "TransactionPreview.error.invalidProps": "TransactionPreview: invalid props",
184
+ "ProgressBar.title": "Progress",
185
+ "ConfirmDialog.title": "Confirmation",
186
+ "ConfirmDialog.options": "Answers",
187
+ "TableView.title": "Data table",
188
+ "TableView.markdown.more": "{n} more rows",
189
+ "MarkdownRenderer.title": "Markdown content",
190
+ "TokenDisplayOnceModal.title": "Token (shown once)",
191
+ "StatusBadge.title": "Status",
192
+ "TokenDisplayOnceModal.notice.once": "The value is shown once, in the app view only.",
193
+ "TokenDisplayOnceModal.field.scope": "Scope",
194
+ "TokenDisplayOnceModal.field.expires": "Expires",
195
+ "TokenDisplayOnceModal.field.fingerprint": "Fingerprint"
102
196
  };
103
197
  var FR = {
104
198
  // ─── Non-forms components ──────────────────────────────────────────────────
@@ -121,6 +215,8 @@ var FR = {
121
215
  "TokenDisplayOnceModal.copied": "Copi\xE9",
122
216
  "StatusBadge.aria.status": "Statut",
123
217
  "StatusBadge.error.invalidProps": "StatusBadge : propri\xE9t\xE9s invalides",
218
+ "Badge.aria.label": "Badge",
219
+ "Badge.error.invalidProps": "Badge : propri\xE9t\xE9s invalides",
124
220
  "EmptyState.error.invalidProps": "EmptyState : propri\xE9t\xE9s invalides",
125
221
  "Toast.aria.close": "Fermer la notification",
126
222
  "Toast.error.invalidProps": "Toast : propri\xE9t\xE9s invalides",
@@ -190,7 +286,99 @@ var FR = {
190
286
  "Select.search.placeholder": "Rechercher\u2026",
191
287
  "Select.empty": "Aucune option disponible",
192
288
  "Select.error.required": "S\xE9lectionnez une option",
193
- "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides"
289
+ "Select.error.invalidProps": "Select : propri\xE9t\xE9s invalides",
290
+ "ModelContext.selection.one": "1 ligne s\xE9lectionn\xE9e",
291
+ "ModelContext.selection.many": "{n} lignes s\xE9lectionn\xE9es",
292
+ "ModelContext.selection.none": "Aucune ligne s\xE9lectionn\xE9e",
293
+ "TableView.sort.by": "Trier par",
294
+ "TableView.select.row": "S\xE9lectionner la ligne",
295
+ "TableView.select.all": "Tout s\xE9lectionner",
296
+ "TableView.loading": "Chargement du tableau",
297
+ "StatCard.title": "Statistique",
298
+ "StatCard.trend.up": "En hausse",
299
+ "StatCard.trend.down": "En baisse",
300
+ "StatCard.trend.flat": "Stable",
301
+ "StatCard.empty.message": "Aucune statistique \xE0 afficher",
302
+ "StatCard.loading": "Chargement de la statistique",
303
+ "StatCard.error.invalidProps": "StatCard : propri\xE9t\xE9s invalides",
304
+ "BalanceCard.title": "Solde",
305
+ "BalanceCard.network": "R\xE9seau",
306
+ "BalanceCard.testnet": "R\xE9seau de test",
307
+ "BalanceCard.address": "Adresse",
308
+ "BalanceCard.contract": "Contrat du jeton",
309
+ "BalanceCard.value": "Valeur",
310
+ "BalanceCard.copy": "Copier l'adresse",
311
+ "BalanceCard.copied": "Adresse copi\xE9e",
312
+ "BalanceCard.empty.message": "Aucun solde \xE0 afficher",
313
+ "BalanceCard.loading": "Chargement du solde",
314
+ "BalanceCard.error.invalidProps": "BalanceCard : propri\xE9t\xE9s invalides",
315
+ "Timeline.title": "Chronologie",
316
+ "Timeline.status.done": "Termin\xE9",
317
+ "Timeline.status.current": "En cours",
318
+ "Timeline.status.pending": "\xC0 venir",
319
+ "Timeline.status.blocked": "Bloqu\xE9",
320
+ "Timeline.completed": "{n} termin\xE9(s)",
321
+ "Timeline.more": "{n} de plus",
322
+ "Timeline.empty.message": "Aucune \xE9tape \xE0 afficher",
323
+ "Timeline.loading": "Chargement de la chronologie",
324
+ "Timeline.error.invalidProps": "Timeline : propri\xE9t\xE9s invalides",
325
+ "MessageFeed.title": "Messages",
326
+ "MessageFeed.unread": "Non lu",
327
+ "MessageFeed.attachment": "Pi\xE8ce jointe",
328
+ "MessageFeed.showing": "Affichage des {n} derniers sur {total}",
329
+ "MessageFeed.empty.message": "Aucun message",
330
+ "MessageFeed.loading": "Chargement des messages",
331
+ "MessageFeed.error.invalidProps": "MessageFeed : propri\xE9t\xE9s invalides",
332
+ "PipelineBoard.title": "Pipeline",
333
+ "PipelineBoard.deals.one": "1 affaire",
334
+ "PipelineBoard.deals.many": "{n} affaires",
335
+ "PipelineBoard.total": "Total",
336
+ "PipelineBoard.stage.empty": "Aucune affaire",
337
+ "PipelineBoard.filter.label": "Afficher les \xE9tapes",
338
+ "PipelineBoard.summary.open": "Ouvrir le tableau complet",
339
+ "PipelineBoard.more": "{n} \xE9tapes de plus",
340
+ "PipelineBoard.empty.message": "Aucun pipeline \xE0 afficher",
341
+ "PipelineBoard.loading": "Chargement du pipeline",
342
+ "PipelineBoard.error.invalidProps": "PipelineBoard : propri\xE9t\xE9s invalides",
343
+ "PipelineBoard.col.stage": "\xC9tape",
344
+ "PipelineBoard.col.deals": "Affaires",
345
+ "TransactionPreview.title": "Aper\xE7u de transaction",
346
+ "TransactionPreview.notice": "NON SIGN\xC9 - pr\xE9paration uniquement",
347
+ "TransactionPreview.kind.transfer": "Transfert",
348
+ "TransactionPreview.kind.swap": "\xC9change",
349
+ "TransactionPreview.testnet": "R\xE9seau de test",
350
+ "TransactionPreview.field.network": "R\xE9seau",
351
+ "TransactionPreview.field.from": "De",
352
+ "TransactionPreview.field.to": "\xC0",
353
+ "TransactionPreview.field.amount": "Montant",
354
+ "TransactionPreview.field.fee": "Frais de r\xE9seau",
355
+ "TransactionPreview.field.sell": "Vendre",
356
+ "TransactionPreview.field.buy": "Acheter",
357
+ "TransactionPreview.field.minReceived": "Minimum re\xE7u",
358
+ "TransactionPreview.field.route": "Itin\xE9raire",
359
+ "TransactionPreview.field.priceImpact": "Impact sur le prix",
360
+ "TransactionPreview.field.expires": "Expiration du devis",
361
+ "TransactionPreview.priceImpact.high": "Impact sur le prix \xE9lev\xE9",
362
+ "TransactionPreview.simulation.success": "Simulation r\xE9ussie",
363
+ "TransactionPreview.simulation.failed": "Simulation \xE9chou\xE9e",
364
+ "TransactionPreview.simulation.unavailable": "Simulation indisponible",
365
+ "TransactionPreview.warnings": "Avertissements",
366
+ "TransactionPreview.handoff": "Ouvrir dans le portefeuille",
367
+ "TransactionPreview.empty.message": "Aucune transaction \xE0 pr\xE9visualiser",
368
+ "TransactionPreview.loading": "Chargement de l'aper\xE7u",
369
+ "TransactionPreview.error.invalidProps": "TransactionPreview : propri\xE9t\xE9s invalides",
370
+ "ProgressBar.title": "Progression",
371
+ "ConfirmDialog.title": "Confirmation",
372
+ "ConfirmDialog.options": "R\xE9ponses",
373
+ "TableView.title": "Tableau de donn\xE9es",
374
+ "TableView.markdown.more": "{n} lignes de plus",
375
+ "MarkdownRenderer.title": "Contenu Markdown",
376
+ "TokenDisplayOnceModal.title": "Jeton (affich\xE9 une fois)",
377
+ "StatusBadge.title": "Statut",
378
+ "TokenDisplayOnceModal.notice.once": "La valeur n'est affich\xE9e qu'une fois, dans la vue de l'application uniquement.",
379
+ "TokenDisplayOnceModal.field.scope": "Port\xE9e",
380
+ "TokenDisplayOnceModal.field.expires": "Expire le",
381
+ "TokenDisplayOnceModal.field.fingerprint": "Empreinte"
194
382
  };
195
383
  var TABLE = {
196
384
  en: EN,
@@ -199,92 +387,279 @@ var TABLE = {
199
387
  function t(key, locale = "en") {
200
388
  return TABLE[locale]?.[key] ?? TABLE.en[key];
201
389
  }
390
+
391
+ // src/components/display/Skeleton.logic.ts
392
+ function getAnimationClass(animation) {
393
+ switch (animation) {
394
+ case "pulse":
395
+ return "animate-pulse";
396
+ case "wave":
397
+ return "skeleton-wave";
398
+ case "none":
399
+ return "";
400
+ }
401
+ }
402
+ function getVariantClass(variant) {
403
+ switch (variant) {
404
+ case "rect":
405
+ return "rounded-md";
406
+ case "circle":
407
+ return "rounded-full";
408
+ case "text":
409
+ return "rounded";
410
+ }
411
+ }
412
+ var SKELETON_BG_CLASS = "bg-gray-200 dark:bg-gray-700";
413
+ function textLineKey(index) {
414
+ return `skeleton-line-${index}`;
415
+ }
416
+ function lastLineWidth(index, total, baseWidth) {
417
+ if (total > 1 && index === total - 1) {
418
+ return "75%";
419
+ }
420
+ return baseWidth;
421
+ }
422
+ var SkeletonPropsSchema = zod.z.object({
423
+ variant: zod.z.enum(["rect", "circle", "text"]).default("rect"),
424
+ width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
425
+ height: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
426
+ count: zod.z.number().int().min(1).max(20).default(1),
427
+ animation: zod.z.enum(["pulse", "wave", "none"]).default("pulse"),
428
+ locale: zod.z.enum(["en", "fr"]).default("en")
429
+ });
430
+ function validateSkeletonProps(raw) {
431
+ return SkeletonPropsSchema.parse(raw);
432
+ }
433
+ function resolveSkeletonDimensions(variant, width, height) {
434
+ const toCss = (v, fallback) => {
435
+ if (v === void 0) return fallback;
436
+ if (typeof v === "number") return `${v}px`;
437
+ return v;
438
+ };
439
+ if (variant === "circle") {
440
+ const size = width !== void 0 ? toCss(width, "40px") : height !== void 0 ? toCss(height, "40px") : "40px";
441
+ return { widthCss: size, heightCss: size };
442
+ }
443
+ if (variant === "text") {
444
+ return {
445
+ widthCss: toCss(width, "100%"),
446
+ heightCss: toCss(height, "16px")
447
+ };
448
+ }
449
+ return {
450
+ widthCss: toCss(width, "100%"),
451
+ heightCss: toCss(height, "100px")
452
+ };
453
+ }
454
+ function SkeletonInner({ variant, width, height, count, animation, locale }) {
455
+ const { widthCss, heightCss } = resolveSkeletonDimensions(variant, width, height);
456
+ const animClass = getAnimationClass(animation);
457
+ const variantClass = getVariantClass(variant);
458
+ const ariaLabel = t("Skeleton.aria.label", locale);
459
+ const baseClass = `${SKELETON_BG_CLASS} ${variantClass} ${animClass}`.trim();
460
+ if (variant === "text") {
461
+ return (
462
+ // biome-ignore lint/a11y/useSemanticElements: role="status" is the correct ARIA pattern for loading indicators; <output> conveys form output semantics which is incorrect here
463
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": ariaLabel, "aria-busy": "true", className: "flex flex-col gap-2", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
464
+ "span",
465
+ {
466
+ className: `block ${baseClass}`,
467
+ style: {
468
+ width: lastLineWidth(i, count, widthCss),
469
+ height: heightCss
470
+ },
471
+ "aria-hidden": "true"
472
+ },
473
+ textLineKey(i)
474
+ )) })
475
+ );
476
+ }
477
+ return /* @__PURE__ */ jsxRuntime.jsx(
478
+ "div",
479
+ {
480
+ role: "status",
481
+ "aria-label": ariaLabel,
482
+ "aria-busy": "true",
483
+ className: `block ${baseClass}`,
484
+ style: { width: widthCss, height: heightCss }
485
+ }
486
+ );
487
+ }
488
+ function Skeleton(raw) {
489
+ try {
490
+ const props = validateSkeletonProps(raw);
491
+ return /* @__PURE__ */ jsxRuntime.jsx(SkeletonInner, { ...props });
492
+ } catch {
493
+ const locale = raw.locale === "fr" ? "fr" : "en";
494
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-red-600 text-sm", children: t("Skeleton.error.invalidProps", locale) });
495
+ }
496
+ }
497
+
498
+ // src/components/display/TableView.logic.ts
499
+ function isMissing(v) {
500
+ return v === null || v === void 0 || v === "";
501
+ }
502
+ var DECIMAL = /^(-)?(\d+)(?:\.(\d+))?$/;
503
+ function parseDecimal(s) {
504
+ const m = DECIMAL.exec(s.trim());
505
+ if (!m) return void 0;
506
+ const int = (m[2] ?? "0").replace(/^0+(?=\d)/, "");
507
+ const frac = (m[3] ?? "").replace(/0+$/, "");
508
+ const zero = int === "0" && frac === "";
509
+ return { neg: m[1] === "-" && !zero, int, frac };
510
+ }
511
+ function compareDecimalStrings(a, b) {
512
+ const x = parseDecimal(a);
513
+ const y = parseDecimal(b);
514
+ if (!x || !y) return 0;
515
+ if (x.neg !== y.neg) return x.neg ? -1 : 1;
516
+ const sign = x.neg ? -1 : 1;
517
+ if (x.int.length !== y.int.length) return sign * (x.int.length < y.int.length ? -1 : 1);
518
+ if (x.int !== y.int) return sign * (x.int < y.int ? -1 : 1);
519
+ const len = Math.max(x.frac.length, y.frac.length);
520
+ const fx = x.frac.padEnd(len, "0");
521
+ const fy = y.frac.padEnd(len, "0");
522
+ if (fx === fy) return 0;
523
+ return sign * (fx < fy ? -1 : 1);
524
+ }
525
+ var collators = /* @__PURE__ */ new Map();
526
+ function collator(locale) {
527
+ let c = collators.get(locale);
528
+ if (!c) {
529
+ c = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
530
+ collators.set(locale, c);
531
+ }
532
+ return c;
533
+ }
534
+ function compareCells(a, b, kind, locale) {
535
+ if (kind === "amount") return compareDecimalStrings(String(a), String(b));
536
+ if (kind === "number") {
537
+ const x = Number(a);
538
+ const y = Number(b);
539
+ if (Number.isNaN(x) || Number.isNaN(y)) return collator(locale).compare(String(a), String(b));
540
+ return x === y ? 0 : x < y ? -1 : 1;
541
+ }
542
+ return collator(locale).compare(String(a), String(b));
543
+ }
544
+ function sortItems(items, getValue, kind, direction, locale) {
545
+ const sign = direction === "asc" ? 1 : -1;
546
+ return items.map((item, index) => ({ item, index, value: getValue(item) })).sort((p, q) => {
547
+ const pm = isMissing(p.value);
548
+ const qm = isMissing(q.value);
549
+ if (pm || qm) return pm && qm ? p.index - q.index : pm ? 1 : -1;
550
+ const c = compareCells(p.value, q.value, kind, locale);
551
+ return c !== 0 ? sign * c : p.index - q.index;
552
+ }).map((e) => e.item);
553
+ }
554
+ function nextSort(current, key) {
555
+ if (!current || current.key !== key) return { key, direction: "asc" };
556
+ return current.direction === "asc" ? { key, direction: "desc" } : void 0;
557
+ }
558
+ function toggleSelection(current, key, mode) {
559
+ if (mode === "none") return new Set(current);
560
+ if (mode === "single") return current.has(key) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([key]);
561
+ const next = new Set(current);
562
+ if (!next.delete(key)) next.add(key);
563
+ return next;
564
+ }
565
+ function toggleAll(current, allKeys) {
566
+ return allKeys.length > 0 && allKeys.every((k) => current.has(k)) ? /* @__PURE__ */ new Set() : new Set(allKeys);
567
+ }
568
+ var ColumnKindSchema = zod.z.enum(["text", "number", "amount", "icon"]);
569
+ var AlignSchema = zod.z.enum(["start", "center", "end"]);
202
570
  var ColumnDefSchema = zod.z.object({
203
571
  key: zod.z.string().min(1),
204
- header: zod.z.string().min(1)
572
+ header: zod.z.string().min(1),
573
+ /** Cell kind: drives comparison and rendering. `amount` = decimal STRING, never a float. */
574
+ type: ColumnKindSchema.default("text"),
575
+ /** Defaults to `end` for `number` / `amount` columns, `start` otherwise. */
576
+ align: AlignSchema.optional(),
577
+ /** CSS width: a number is pixels. */
578
+ width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
579
+ sortable: zod.z.boolean().default(false),
580
+ /**
581
+ * Values of this column are masked in the A-1 markdown fallback and kept out of the HTML
582
+ * props of a tool result (delivered via the result `_meta`), never written to `content[]`.
583
+ */
584
+ sensitive: zod.z.boolean().default(false)
205
585
  // render is a runtime function — excluded from Zod, typed separately
206
586
  });
587
+ var SortSchema = zod.z.object({ key: zod.z.string().min(1), direction: zod.z.enum(["asc", "desc"]) });
207
588
  var TableViewPropsSchema = zod.z.object({
208
589
  columns: zod.z.array(ColumnDefSchema).min(1, "TableView requires at least one column"),
590
+ /**
591
+ * Serialisable rows, for views built through `createMosaicResource` (the React props carry
592
+ * `rows` separately).
593
+ */
594
+ rows: zod.z.array(zod.z.record(zod.z.unknown())).default([]),
209
595
  virtualizeThreshold: zod.z.number().int().positive().default(100),
210
596
  ariaLabel: zod.z.string().min(1),
211
- locale: zod.z.enum(["en", "fr"]).default("en")
597
+ locale: zod.z.enum(["en", "fr"]).default("en"),
598
+ selectionMode: zod.z.enum(["none", "single", "multiple"]).default("none"),
599
+ /** Initial sort. */
600
+ sort: SortSchema.optional(),
601
+ stickyHeader: zod.z.boolean().default(false),
602
+ /** Scroll region height when `stickyHeader` is on (pixels). */
603
+ maxHeight: zod.z.number().int().positive().default(400),
604
+ density: zod.z.enum(["comfortable", "compact"]).default("comfortable"),
605
+ loading: zod.z.boolean().default(false)
212
606
  });
213
607
  function validateTableViewProps(raw) {
214
608
  return TableViewPropsSchema.parse(raw);
215
609
  }
216
610
  var OVERSCAN = 5;
217
- function TableView(props) {
218
- let validatedThreshold = 100;
611
+ var SKELETON_ROWS = ["head", "r1", "r2", "r3", "r4", "r5"];
612
+ function resolveLocale(l) {
613
+ return l === "fr" ? "fr" : "en";
614
+ }
615
+ function validate(props) {
219
616
  try {
220
617
  const validated = validateTableViewProps({
221
- columns: props.columns.map(({ key, header }) => ({ key, header })),
618
+ columns: props.columns.map(({ key, header, type, align, width, sortable }) => ({
619
+ key,
620
+ header,
621
+ type,
622
+ align,
623
+ width,
624
+ sortable
625
+ })),
222
626
  virtualizeThreshold: props.virtualizeThreshold,
223
627
  ariaLabel: props.ariaLabel,
224
- locale: props.locale
628
+ locale: props.locale,
629
+ selectionMode: props.selectionMode,
630
+ density: props.density
225
631
  });
226
- validatedThreshold = validated.virtualizeThreshold;
632
+ return validated.virtualizeThreshold;
227
633
  } catch {
228
- const locale = props.locale === "fr" ? "fr" : "en";
229
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
634
+ return void 0;
230
635
  }
231
- const isVirtual = props.rows.length > validatedThreshold;
232
- const resolvedLocale = props.locale === "fr" ? "fr" : "en";
233
- if (isVirtual) {
234
- return /* @__PURE__ */ jsxRuntime.jsx(
235
- VirtualTable,
236
- {
237
- rows: props.rows,
238
- columns: props.columns,
239
- ariaLabel: props.ariaLabel,
240
- locale: resolvedLocale,
241
- onRowClick: props.onRowClick
242
- }
243
- );
636
+ }
637
+ function TableView(props) {
638
+ const threshold = validate(props);
639
+ if (threshold === void 0) {
640
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
244
641
  }
245
642
  return /* @__PURE__ */ jsxRuntime.jsx(
246
- StaticTable,
643
+ TableCore,
247
644
  {
645
+ ...props,
248
646
  rows: props.rows,
249
- columns: props.columns,
250
- ariaLabel: props.ariaLabel,
251
- locale: resolvedLocale,
252
- onRowClick: props.onRowClick
647
+ virtualizeThreshold: threshold,
648
+ locale: resolveLocale(props.locale)
253
649
  }
254
650
  );
255
651
  }
256
652
  function StreamingTableView(props) {
257
- let validatedThreshold = 100;
258
- try {
259
- const validated = validateTableViewProps({
260
- columns: props.columns.map(({ key, header }) => ({ key, header })),
261
- virtualizeThreshold: props.virtualizeThreshold,
262
- ariaLabel: props.ariaLabel,
263
- locale: props.locale
264
- });
265
- validatedThreshold = validated.virtualizeThreshold;
266
- } catch {
267
- const locale = props.locale === "fr" ? "fr" : "en";
268
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", locale) });
653
+ const threshold = validate(props);
654
+ if (threshold === void 0) {
655
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("TableView.error.invalidProps", resolveLocale(props.locale)) });
269
656
  }
270
- return /* @__PURE__ */ jsxRuntime.jsx(
271
- StreamingTableViewInner,
272
- {
273
- ...props,
274
- virtualizeThreshold: validatedThreshold,
275
- onRowClick: props.onRowClick
276
- }
277
- );
657
+ return /* @__PURE__ */ jsxRuntime.jsx(StreamingTableViewInner, { ...props, virtualizeThreshold: threshold });
278
658
  }
279
659
  function StreamingTableViewInner({
280
- columns,
281
660
  rows$,
282
- virtualizeThreshold = 100,
283
- ariaLabel,
284
- locale,
285
- onRowClick
661
+ ...rest
286
662
  }) {
287
- const resolvedLocale = locale === "fr" ? "fr" : "en";
288
663
  const [rows, setRows] = react.useState([]);
289
664
  react.useEffect(() => {
290
665
  setRows([]);
@@ -295,87 +670,282 @@ function StreamingTableViewInner({
295
670
  sub.unsubscribe();
296
671
  };
297
672
  }, [rows$]);
298
- const isVirtual = rows.length > virtualizeThreshold;
299
- if (isVirtual) {
673
+ return /* @__PURE__ */ jsxRuntime.jsx(TableCore, { ...rest, rows, locale: resolveLocale(rest.locale) });
674
+ }
675
+ function kindOf(col) {
676
+ return col.type ?? "text";
677
+ }
678
+ function alignOf(col) {
679
+ return col.align ?? (col.type === "amount" || col.type === "number" ? "end" : "start");
680
+ }
681
+ function cellText(row, key) {
682
+ return String(row[key] ?? "");
683
+ }
684
+ function safeIconSrc(v) {
685
+ if (typeof v !== "string") return void 0;
686
+ return /^(https?:\/\/|data:image\/)/i.test(v) ? v : void 0;
687
+ }
688
+ function renderCell(col, row) {
689
+ if (col.render) {
690
+ return col.render(row);
691
+ }
692
+ const value = row[col.key];
693
+ if (col.type === "icon") {
694
+ const src = safeIconSrc(value);
695
+ return src ? /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "", width: 20, height: 20, style: { verticalAlign: "middle" } }) : null;
696
+ }
697
+ if (col.type === "amount" || col.type === "number") {
698
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: String(value ?? "") });
699
+ }
700
+ return String(value ?? "");
701
+ }
702
+ function TableCore(props) {
703
+ const {
704
+ columns,
705
+ rows,
706
+ ariaLabel,
707
+ locale,
708
+ selectionMode = "none",
709
+ stickyHeader = false,
710
+ density = "comfortable"
711
+ } = props;
712
+ const [innerSort, setInnerSort] = react.useState(props.defaultSort);
713
+ const sort = props.sort === void 0 ? innerSort : props.sort ?? void 0;
714
+ const onSort = (key) => {
715
+ const next = nextSort(sort, key);
716
+ if (props.sort === void 0) setInnerSort(next);
717
+ props.onSortChange?.(next);
718
+ };
719
+ const items = react.useMemo(
720
+ () => rows.map((row, i) => {
721
+ const id = row.id;
722
+ return { row, key: id != null ? String(id) : `row-${i}` };
723
+ }),
724
+ [rows]
725
+ );
726
+ const sorted = react.useMemo(() => {
727
+ const col = sort ? columns.find((c) => c.key === sort.key) : void 0;
728
+ if (!sort || !col) return items;
729
+ return sortItems(
730
+ items,
731
+ (it) => it.row[col.key],
732
+ kindOf(col),
733
+ sort.direction,
734
+ locale
735
+ );
736
+ }, [items, sort, columns, locale]);
737
+ const [innerSelected, setInnerSelected] = react.useState(
738
+ () => new Set(props.defaultSelectedKeys ?? [])
739
+ );
740
+ const selected = props.selectedKeys ? new Set(props.selectedKeys) : innerSelected;
741
+ const commit = (next) => {
742
+ if (!props.selectedKeys) setInnerSelected(next);
743
+ const chosen = sorted.filter((it) => next.has(it.key));
744
+ props.onSelectionChange?.(
745
+ chosen.map((it) => it.row),
746
+ chosen.map((it) => it.key)
747
+ );
748
+ };
749
+ const selection = selectionMode === "none" ? void 0 : {
750
+ mode: selectionMode,
751
+ selected,
752
+ toggle: (key) => commit(toggleSelection(selected, key, selectionMode)),
753
+ toggleAll: () => commit(
754
+ toggleAll(
755
+ selected,
756
+ sorted.map((it) => it.key)
757
+ )
758
+ )
759
+ };
760
+ if (props.loading) {
761
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-busy": "true", "aria-label": t("TableView.loading", locale), lang: locale, children: SKELETON_ROWS.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
762
+ Skeleton,
763
+ {
764
+ variant: "rect",
765
+ height: i === 0 ? 24 : 36,
766
+ locale
767
+ },
768
+ SKELETON_ROWS[i]
769
+ )) });
770
+ }
771
+ const content = {
772
+ items: sorted,
773
+ columns,
774
+ ariaLabel,
775
+ locale,
776
+ onRowClick: props.onRowClick,
777
+ sort,
778
+ onSort,
779
+ selection,
780
+ sticky: stickyHeader,
781
+ density
782
+ };
783
+ const isVirtual = rows.length > props.virtualizeThreshold;
784
+ if (isVirtual) return /* @__PURE__ */ jsxRuntime.jsx(VirtualTable, { ...content });
785
+ if (stickyHeader) {
300
786
  return /* @__PURE__ */ jsxRuntime.jsx(
301
- VirtualTable,
787
+ "div",
302
788
  {
303
- rows,
304
- columns,
305
- ariaLabel,
306
- locale: resolvedLocale,
307
- onRowClick
789
+ tabIndex: 0,
790
+ role: "region",
791
+ "aria-label": ariaLabel,
792
+ style: { maxHeight: props.maxHeight ?? 400, overflow: "auto" },
793
+ children: /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content })
308
794
  }
309
795
  );
310
796
  }
797
+ return /* @__PURE__ */ jsxRuntime.jsx(StaticTable, { ...content });
798
+ }
799
+ var CELL_PADDING = { comfortable: void 0, compact: "2px 6px" };
800
+ function HeaderRow({
801
+ columns,
802
+ locale,
803
+ sort,
804
+ onSort,
805
+ selection,
806
+ sticky,
807
+ density,
808
+ items
809
+ }) {
810
+ const padding = CELL_PADDING[density];
811
+ const stickyStyle = sticky ? { position: "sticky", top: 0, background: "var(--mosaic-color-background, Canvas)" } : {};
812
+ const allSelected = items.length > 0 && items.every((it) => selection?.selected.has(it.key));
813
+ const someSelected = items.some((it) => selection?.selected.has(it.key));
814
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
815
+ selection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { ...stickyStyle, padding, width: 32 }, children: selection.mode === "multiple" ? /* @__PURE__ */ jsxRuntime.jsx(
816
+ "input",
817
+ {
818
+ type: "checkbox",
819
+ "aria-label": t("TableView.select.all", locale),
820
+ checked: allSelected,
821
+ ref: (el) => {
822
+ if (el) el.indeterminate = someSelected && !allSelected;
823
+ },
824
+ onChange: selection.toggleAll
825
+ }
826
+ ) : null }) : null,
827
+ columns.map((col) => {
828
+ const dir = sort?.key === col.key ? sort.direction : void 0;
829
+ return /* @__PURE__ */ jsxRuntime.jsx(
830
+ "th",
831
+ {
832
+ scope: "col",
833
+ "aria-sort": col.sortable ? dir === "asc" ? "ascending" : dir === "desc" ? "descending" : "none" : void 0,
834
+ style: {
835
+ ...stickyStyle,
836
+ padding,
837
+ textAlign: alignOf(col),
838
+ ...col.width !== void 0 ? { width: col.width } : {}
839
+ },
840
+ children: col.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
841
+ "button",
842
+ {
843
+ type: "button",
844
+ "aria-label": `${t("TableView.sort.by", locale)} ${col.header}`,
845
+ onClick: () => onSort(col.key),
846
+ style: { all: "inherit", cursor: "pointer", padding: 0 },
847
+ children: [
848
+ col.header,
849
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: dir === "asc" ? " \u25B2" : dir === "desc" ? " \u25BC" : "" })
850
+ ]
851
+ }
852
+ ) : col.header
853
+ },
854
+ col.key
855
+ );
856
+ })
857
+ ] });
858
+ }
859
+ function SelectCell({
860
+ item,
861
+ columns,
862
+ selection,
863
+ locale,
864
+ padding
865
+ }) {
866
+ const first = columns[0];
867
+ const label = `${t("TableView.select.row", locale)}: ${first ? cellText(item.row, first.key) : item.key}`;
311
868
  return /* @__PURE__ */ jsxRuntime.jsx(
312
- StaticTable,
869
+ "td",
313
870
  {
314
- rows,
315
- columns,
316
- ariaLabel,
317
- locale: resolvedLocale,
318
- onRowClick
871
+ style: { padding, width: 32 },
872
+ onClick: (e) => e.stopPropagation(),
873
+ onKeyDown: (e) => e.stopPropagation(),
874
+ children: /* @__PURE__ */ jsxRuntime.jsx(
875
+ "input",
876
+ {
877
+ type: selection.mode === "multiple" ? "checkbox" : "radio",
878
+ name: selection.mode === "single" ? "mosaic-table-selection" : void 0,
879
+ "aria-label": label,
880
+ checked: selection.selected.has(item.key),
881
+ onChange: () => selection.toggle(item.key),
882
+ onKeyDown: (e) => e.stopPropagation()
883
+ }
884
+ )
319
885
  }
320
886
  );
321
887
  }
322
- function renderCell(col, row) {
323
- if (col.render) {
324
- return col.render(row);
325
- }
326
- return String(row[col.key] ?? "");
888
+ function rowActivation(onRowClick, row, index) {
889
+ if (!onRowClick) return {};
890
+ return {
891
+ role: "button",
892
+ tabIndex: 0,
893
+ onClick: () => onRowClick(row, index),
894
+ onKeyDown: (e) => {
895
+ if (e.key === "Enter" || e.key === " ") {
896
+ e.preventDefault();
897
+ onRowClick(row, index);
898
+ }
899
+ }
900
+ };
327
901
  }
328
- function StaticTable({
329
- rows,
330
- columns,
331
- ariaLabel,
332
- locale,
333
- onRowClick
334
- }) {
902
+ function StaticTable(props) {
903
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
335
904
  const emptyMsg = t("TableView.empty.message", locale);
905
+ const padding = CELL_PADDING[density];
906
+ const colSpan = columns.length + (selection ? 1 : 0);
336
907
  return /* @__PURE__ */ jsxRuntime.jsxs(
337
908
  "table",
338
909
  {
339
910
  "aria-label": ariaLabel,
340
- "aria-rowcount": rows.length,
911
+ "aria-rowcount": items.length,
912
+ "data-density": density,
341
913
  style: { width: "100%", borderCollapse: "collapse" },
342
914
  children: [
343
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
344
- /* @__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(
915
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props }) }),
916
+ /* @__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(
345
917
  "tr",
346
918
  {
347
919
  "aria-rowindex": rowIndex + 2,
348
- ...onRowClick ? {
349
- role: "button",
350
- tabIndex: 0,
351
- style: { cursor: "pointer" },
352
- onClick: () => onRowClick(row, rowIndex),
353
- onKeyDown: (e) => {
354
- if (e.key === "Enter" || e.key === " ") {
355
- e.preventDefault();
356
- onRowClick(row, rowIndex);
920
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
921
+ ...onRowClick ? { style: { cursor: "pointer" } } : {},
922
+ ...rowActivation(onRowClick, item.row, rowIndex),
923
+ children: [
924
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
925
+ SelectCell,
926
+ {
927
+ item,
928
+ columns,
929
+ selection,
930
+ locale,
931
+ padding
357
932
  }
358
- }
359
- } : {},
360
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(col, row) }, col.key))
933
+ ) : null,
934
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
935
+ ]
361
936
  },
362
- row.id != null ? String(row.id) : `row-${rowIndex}`
937
+ item.key
363
938
  )) })
364
939
  ]
365
940
  }
366
941
  );
367
942
  }
368
- function VirtualTable({
369
- rows,
370
- columns,
371
- ariaLabel,
372
- // locale reserved for future virtual-mode empty/pagination strings
373
- locale: _locale,
374
- onRowClick
375
- }) {
943
+ function VirtualTable(props) {
944
+ const { items, columns, ariaLabel, locale, onRowClick, selection, density } = props;
376
945
  const scrollRef = react.useRef(null);
946
+ const padding = CELL_PADDING[density];
377
947
  const rowVirtualizer = reactVirtual.useVirtualizer({
378
- count: rows.length,
948
+ count: items.length,
379
949
  /* v8 ignore next 1 */
380
950
  getScrollElement: () => scrollRef.current,
381
951
  estimateSize: react.useCallback(() => 40, []),
@@ -388,18 +958,19 @@ function VirtualTable({
388
958
  {
389
959
  "data-virtual": "true",
390
960
  ref: scrollRef,
391
- tabIndex: 0,
392
961
  role: "region",
962
+ tabIndex: 0,
393
963
  "aria-label": ariaLabel,
394
964
  style: { height: 400, overflow: "auto", position: "relative" },
395
965
  children: /* @__PURE__ */ jsxRuntime.jsxs(
396
966
  "table",
397
967
  {
398
968
  "aria-label": ariaLabel,
399
- "aria-rowcount": rows.length,
969
+ "aria-rowcount": items.length,
970
+ "data-density": density,
400
971
  style: { width: "100%", borderCollapse: "collapse" },
401
972
  children: [
402
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", children: col.header }, col.key)) }) }),
973
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx(HeaderRow, { ...props, sticky: true }) }),
403
974
  /* @__PURE__ */ jsxRuntime.jsx(
404
975
  "tbody",
405
976
  {
@@ -409,11 +980,12 @@ function VirtualTable({
409
980
  display: "block"
410
981
  },
411
982
  children: virtualItems.map((virtualRow) => {
412
- const row = rows[virtualRow.index] ?? {};
413
- return /* @__PURE__ */ jsxRuntime.jsx(
983
+ const item = items[virtualRow.index] ?? { row: {}, key: "" };
984
+ return /* @__PURE__ */ jsxRuntime.jsxs(
414
985
  "tr",
415
986
  {
416
987
  "aria-rowindex": virtualRow.index + 2,
988
+ "data-selected": selection?.selected.has(item.key) ? "true" : void 0,
417
989
  style: {
418
990
  position: "absolute",
419
991
  top: 0,
@@ -423,20 +995,22 @@ function VirtualTable({
423
995
  display: "flex",
424
996
  ...onRowClick ? { cursor: "pointer" } : {}
425
997
  },
426
- ...onRowClick ? {
427
- role: "button",
428
- tabIndex: 0,
429
- onClick: () => onRowClick(row, virtualRow.index),
430
- onKeyDown: (e) => {
431
- if (e.key === "Enter" || e.key === " ") {
432
- e.preventDefault();
433
- onRowClick(row, virtualRow.index);
998
+ ...rowActivation(onRowClick, item.row, virtualRow.index),
999
+ children: [
1000
+ selection ? /* @__PURE__ */ jsxRuntime.jsx(
1001
+ SelectCell,
1002
+ {
1003
+ item,
1004
+ columns,
1005
+ selection,
1006
+ locale,
1007
+ padding
434
1008
  }
435
- }
436
- } : {},
437
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1 }, children: renderCell(col, row) }, col.key))
1009
+ ) : null,
1010
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { flex: 1, padding, textAlign: alignOf(col) }, children: renderCell(col, item.row) }, col.key))
1011
+ ]
438
1012
  },
439
- row.id != null ? String(row.id) : `vrow-${virtualRow.index}`
1013
+ item.key || `vrow-${virtualRow.index}`
440
1014
  );
441
1015
  })
442
1016
  }
@@ -460,122 +1034,900 @@ function validateEmptyStateProps(raw) {
460
1034
  return EmptyStatePropsSchema.parse(raw);
461
1035
  }
462
1036
 
463
- // src/components/display/Skeleton.logic.ts
464
- function getAnimationClass(animation) {
465
- switch (animation) {
466
- case "pulse":
467
- return "animate-pulse";
468
- case "wave":
469
- return "skeleton-wave";
470
- case "none":
471
- return "";
1037
+ // src/components/display/EmptyState.logic.ts
1038
+ var ACTION_VARIANT_CLASSES = {
1039
+ primary: "inline-flex items-center justify-center rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-slate-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900 disabled:pointer-events-none disabled:opacity-50",
1040
+ secondary: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400 disabled:pointer-events-none disabled:opacity-50"
1041
+ };
1042
+ function EmptyStateInner({
1043
+ icon,
1044
+ title,
1045
+ description,
1046
+ action,
1047
+ locale: _locale
1048
+ }) {
1049
+ const variant = action?.variant ?? "primary";
1050
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1051
+ "section",
1052
+ {
1053
+ "aria-label": title,
1054
+ className: "flex flex-col items-center justify-center gap-4 px-6 py-12 text-center",
1055
+ children: [
1056
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "text-slate-400", children: icon }),
1057
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-semibold text-slate-900", children: title }),
1058
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm leading-relaxed text-slate-500", children: description }),
1059
+ action && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: action.onClick, className: ACTION_VARIANT_CLASSES[variant], children: action.label })
1060
+ ]
1061
+ }
1062
+ );
1063
+ }
1064
+ function EmptyState(raw) {
1065
+ try {
1066
+ const parsed = validateEmptyStateProps(raw);
1067
+ return /* @__PURE__ */ jsxRuntime.jsx(
1068
+ EmptyStateInner,
1069
+ {
1070
+ ...parsed,
1071
+ icon: raw.icon,
1072
+ action: parsed.action && raw.action?.onClick ? { ...parsed.action, onClick: raw.action.onClick } : void 0
1073
+ }
1074
+ );
1075
+ } catch (err) {
1076
+ console.error("[EmptyState] Invalid props \u2014 component will not render.", err);
1077
+ const locale = raw.locale === "fr" ? "fr" : "en";
1078
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("EmptyState.error.invalidProps", locale) });
472
1079
  }
473
1080
  }
474
- function getVariantClass(variant) {
475
- switch (variant) {
476
- case "rect":
477
- return "rounded-md";
478
- case "circle":
479
- return "rounded-full";
480
- case "text":
481
- return "rounded";
1081
+ var StatCardPropsSchema = zod.z.object({
1082
+ label: zod.z.string().default(""),
1083
+ value: zod.z.string().default(""),
1084
+ unit: zod.z.string().optional(),
1085
+ trend: zod.z.enum(["up", "down", "flat"]).optional(),
1086
+ /** Pre-formatted change, e.g. "+4.2%". */
1087
+ change: zod.z.string().optional(),
1088
+ tone: zod.z.enum(["neutral", "success", "warning", "danger"]).default("neutral"),
1089
+ description: zod.z.string().optional(),
1090
+ loading: zod.z.boolean().default(false),
1091
+ locale: zod.z.enum(["en", "fr"]).default("en")
1092
+ });
1093
+ function validateStatCardProps(raw) {
1094
+ return StatCardPropsSchema.parse(raw);
1095
+ }
1096
+ var TONE_CLASSES = {
1097
+ neutral: "text-slate-900",
1098
+ success: "text-green-700",
1099
+ warning: "text-amber-700",
1100
+ danger: "text-red-700"
1101
+ };
1102
+ var TREND_ARROW = { up: "\u25B2", down: "\u25BC", flat: "\u25AC" };
1103
+ function StatCard(raw) {
1104
+ const parsed = StatCardPropsSchema.safeParse(raw);
1105
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1106
+ if (!parsed.success) {
1107
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("StatCard.error.invalidProps", locale) });
1108
+ }
1109
+ const p = parsed.data;
1110
+ if (p.loading) {
1111
+ 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 }) });
1112
+ }
1113
+ if (p.label === "" && p.value === "") {
1114
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("StatCard.empty.message", p.locale), locale: p.locale });
482
1115
  }
1116
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1117
+ "section",
1118
+ {
1119
+ "aria-label": `${t("StatCard.title", p.locale)}: ${p.label}`,
1120
+ lang: p.locale,
1121
+ className: "flex flex-col gap-1 rounded-lg border border-slate-200 bg-white p-4",
1122
+ children: [
1123
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.label }),
1124
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-2xl font-semibold tabular-nums ${TONE_CLASSES[p.tone]}`, children: [
1125
+ p.value,
1126
+ p.unit ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-base font-normal text-slate-500", children: p.unit }) : null
1127
+ ] }),
1128
+ p.trend || p.change ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-sm text-slate-600", children: [
1129
+ p.trend ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1130
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: TREND_ARROW[p.trend] }),
1131
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`StatCard.trend.${p.trend}`, p.locale) })
1132
+ ] }) : null,
1133
+ p.change ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: p.change }) : null
1134
+ ] }) : null,
1135
+ p.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: p.description }) : null
1136
+ ]
1137
+ }
1138
+ );
483
1139
  }
484
- var SKELETON_BG_CLASS = "bg-gray-200 dark:bg-gray-700";
485
- function textLineKey(index) {
486
- return `skeleton-line-${index}`;
1140
+
1141
+ // src/components/shared/markdown.ts
1142
+ var SPECIAL = /([\\`*_[\]()<>&!|~])/g;
1143
+ var BREAKS = /* @__PURE__ */ new Set([10, 11, 12, 13, 133, 8232, 8233]);
1144
+ function flatten(text) {
1145
+ let out = "";
1146
+ let gap = false;
1147
+ for (const c of text) {
1148
+ const n = c.codePointAt(0) ?? 0;
1149
+ if (BREAKS.has(n)) {
1150
+ gap = true;
1151
+ continue;
1152
+ }
1153
+ if (n < 32 && n !== 9) continue;
1154
+ if (n === 127) continue;
1155
+ if (gap) out += " ";
1156
+ gap = false;
1157
+ out += c;
1158
+ }
1159
+ return gap ? `${out} ` : out;
487
1160
  }
488
- function lastLineWidth(index, total, baseWidth) {
489
- if (total > 1 && index === total - 1) {
490
- return "75%";
1161
+ function mdEscape(text) {
1162
+ const flat = flatten(text).replace(SPECIAL, "\\$1").replace(/\b(https?|ftp):\/\//gi, "$1:\\/\\/").replace(/\bwww\./gi, (m) => `${m.slice(0, 3)}\\.`);
1163
+ return flat.replace(/^(\s*)([#>+-])/, "$1\\$2").replace(/^(\s*\d+)([.)])(?=\s|$)/, "$1\\$2");
1164
+ }
1165
+ function mdUrl(href) {
1166
+ return href.replace(
1167
+ /[()[\]\\<>`\s]/g,
1168
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`
1169
+ );
1170
+ }
1171
+ function mdCode(text) {
1172
+ const flat = flatten(text);
1173
+ const longest = Math.max(0, ...(flat.match(/`+/g) ?? []).map((r) => r.length));
1174
+ const fence = "`".repeat(longest + 1);
1175
+ return `${fence} ${flat} ${fence}`;
1176
+ }
1177
+ function mdTable(headers, rows) {
1178
+ const line = (cells) => `| ${cells.join(" | ")} |`;
1179
+ return [line(headers), line(headers.map(() => "---")), ...rows.map(line)].join("\n");
1180
+ }
1181
+ function mdHeading(text, level = 1) {
1182
+ return `${"#".repeat(level)} ${text}`;
1183
+ }
1184
+
1185
+ // src/components/display/StatCard.markdown.ts
1186
+ function statCardToMarkdown(props, locale = "en") {
1187
+ const parsed = StatCardPropsSchema.safeParse(props);
1188
+ const title = mdHeading(t("StatCard.title", locale));
1189
+ if (!parsed.success || parsed.data.label === "" && parsed.data.value === "") {
1190
+ return `${title}
1191
+
1192
+ ${t("StatCard.empty.message", locale)}
1193
+ `;
491
1194
  }
492
- return baseWidth;
1195
+ const { label, value, unit, trend, change, description } = parsed.data;
1196
+ const amount = unit ? `${value} ${unit}` : value;
1197
+ const movement = [trend ? t(`StatCard.trend.${trend}`, locale) : "", change ?? ""].filter(Boolean).join(" ");
1198
+ const line = `- **${mdEscape(label)}**: ${mdEscape(amount)}${movement ? ` (${mdEscape(movement)})` : ""}`;
1199
+ return `${title}
1200
+
1201
+ ${line}
1202
+ ${description ? `
1203
+ ${mdEscape(description)}
1204
+ ` : ""}`;
493
1205
  }
494
- var SkeletonPropsSchema = zod.z.object({
495
- variant: zod.z.enum(["rect", "circle", "text"]).default("rect"),
496
- width: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
497
- height: zod.z.union([zod.z.string().min(1), zod.z.number().positive()]).optional(),
498
- count: zod.z.number().int().min(1).max(20).default(1),
499
- animation: zod.z.enum(["pulse", "wave", "none"]).default("pulse"),
1206
+ var BadgePropsSchema = zod.z.object({
1207
+ label: zod.z.string(),
1208
+ variant: zod.z.enum(["default", "primary", "success", "warning", "danger", "info", "neutral"]).default("default"),
1209
+ dot: zod.z.boolean().optional(),
1210
+ count: zod.z.number().int().nonnegative().optional(),
500
1211
  locale: zod.z.enum(["en", "fr"]).default("en")
501
1212
  });
502
- function validateSkeletonProps(raw) {
503
- return SkeletonPropsSchema.parse(raw);
1213
+ function validateBadgeProps(raw) {
1214
+ return BadgePropsSchema.parse(raw);
504
1215
  }
505
- function resolveSkeletonDimensions(variant, width, height) {
506
- const toCss = (v, fallback) => {
507
- if (v === void 0) return fallback;
508
- if (typeof v === "number") return `${v}px`;
509
- return v;
510
- };
511
- if (variant === "circle") {
512
- const size = width !== void 0 ? toCss(width, "40px") : height !== void 0 ? toCss(height, "40px") : "40px";
513
- return { widthCss: size, heightCss: size };
1216
+ var variantClasses = {
1217
+ default: "bg-slate-100 text-slate-700 ring-1 ring-slate-200",
1218
+ primary: "bg-indigo-100 text-indigo-800 ring-1 ring-indigo-300",
1219
+ success: "bg-green-100 text-green-800 ring-1 ring-green-300",
1220
+ warning: "bg-amber-100 text-amber-800 ring-1 ring-amber-300",
1221
+ danger: "bg-red-100 text-red-800 ring-1 ring-red-300",
1222
+ info: "bg-blue-100 text-blue-800 ring-1 ring-blue-300",
1223
+ neutral: "bg-slate-100 text-slate-800 ring-1 ring-slate-300"
1224
+ };
1225
+ var dotClasses = {
1226
+ default: "bg-slate-400",
1227
+ primary: "bg-indigo-500",
1228
+ success: "bg-green-500",
1229
+ warning: "bg-amber-500",
1230
+ danger: "bg-red-500",
1231
+ info: "bg-blue-500",
1232
+ neutral: "bg-slate-400"
1233
+ };
1234
+ function BadgeInner({ label, variant, dot, count, locale }) {
1235
+ const ariaLabel = t("Badge.aria.label", locale);
1236
+ const displayCount = count !== void 0 ? ` (${count})` : "";
1237
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1238
+ "span",
1239
+ {
1240
+ "aria-label": `${ariaLabel}: ${label}${displayCount}`,
1241
+ lang: locale,
1242
+ className: `inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${variantClasses[variant]}`,
1243
+ children: [
1244
+ dot && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: `h-1.5 w-1.5 rounded-full ${dotClasses[variant]}` }),
1245
+ label,
1246
+ count !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "font-semibold tabular-nums", children: count })
1247
+ ]
1248
+ }
1249
+ );
1250
+ }
1251
+ function Badge(raw) {
1252
+ try {
1253
+ const props = validateBadgeProps(raw);
1254
+ return /* @__PURE__ */ jsxRuntime.jsx(BadgeInner, { ...props });
1255
+ } catch {
1256
+ const locale = raw.locale === "fr" ? "fr" : "en";
1257
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", children: t("Badge.error.invalidProps", locale) });
514
1258
  }
515
- if (variant === "text") {
516
- return {
517
- widthCss: toCss(width, "100%"),
518
- heightCss: toCss(height, "16px")
519
- };
1259
+ }
1260
+
1261
+ // src/components/shared/decimal.ts
1262
+ var DECIMAL2 = /^(-)?(\d+)(?:\.(\d+))?$/;
1263
+ function sumDecimalStrings(values) {
1264
+ let scale = 0;
1265
+ const parsed = [];
1266
+ for (const v of values) {
1267
+ const m = DECIMAL2.exec(v);
1268
+ if (!m) return void 0;
1269
+ const frac2 = m[3] ?? "";
1270
+ scale = Math.max(scale, frac2.length);
1271
+ parsed.push({ neg: m[1] === "-", int: m[2] ?? "0", frac: frac2 });
520
1272
  }
521
- return {
522
- widthCss: toCss(width, "100%"),
523
- heightCss: toCss(height, "100px")
1273
+ let total = 0n;
1274
+ for (const p of parsed) {
1275
+ const n = BigInt(p.int + p.frac.padEnd(scale, "0"));
1276
+ total += p.neg ? -n : n;
1277
+ }
1278
+ const neg = total < 0n;
1279
+ const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
1280
+ const int = digits.slice(0, digits.length - scale);
1281
+ const frac = digits.slice(digits.length - scale).replace(/0+$/, "");
1282
+ return `${neg ? "-" : ""}${int}${frac ? `.${frac}` : ""}`;
1283
+ }
1284
+ function formatDecimalString(value, locale, currency) {
1285
+ if (!DECIMAL2.test(value)) return value;
1286
+ try {
1287
+ const nf = new Intl.NumberFormat(
1288
+ locale,
1289
+ currency ? { style: "currency", currency } : { maximumFractionDigits: 20 }
1290
+ );
1291
+ return nf.format(value);
1292
+ } catch {
1293
+ return value;
1294
+ }
1295
+ }
1296
+ function truncateAddress(address, head = 6, tail = 4) {
1297
+ return address.length <= head + tail + 1 ? address : `${address.slice(0, head)}\u2026${address.slice(address.length - tail)}`;
1298
+ }
1299
+ var BalanceCardPropsSchema = zod.z.object({
1300
+ token: zod.z.string().default(""),
1301
+ balance: zod.z.string().default(""),
1302
+ address: zod.z.string().default(""),
1303
+ tokenAddress: zod.z.string().optional(),
1304
+ decimals: zod.z.number().int().nonnegative().optional(),
1305
+ network: zod.z.string().optional(),
1306
+ testnet: zod.z.boolean().default(false),
1307
+ /** Decimal string in `currency`. */
1308
+ usdValue: zod.z.string().optional(),
1309
+ currency: zod.z.string().default("USD"),
1310
+ loading: zod.z.boolean().default(false),
1311
+ locale: zod.z.enum(["en", "fr"]).default("en")
1312
+ });
1313
+ function validateBalanceCardProps(raw) {
1314
+ return BalanceCardPropsSchema.parse(raw);
1315
+ }
1316
+ function BalanceCard(raw) {
1317
+ const parsed = BalanceCardPropsSchema.safeParse(raw);
1318
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1319
+ const [copied, setCopied] = react.useState(false);
1320
+ if (!parsed.success) {
1321
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("BalanceCard.error.invalidProps", locale) });
1322
+ }
1323
+ const p = parsed.data;
1324
+ if (p.loading) {
1325
+ 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 }) });
1326
+ }
1327
+ if (p.token === "" && p.balance === "") {
1328
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("BalanceCard.empty.message", p.locale), locale: p.locale });
1329
+ }
1330
+ const canCopy = typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
1331
+ const copy = async () => {
1332
+ try {
1333
+ await navigator.clipboard.writeText(p.address);
1334
+ setCopied(true);
1335
+ } catch {
1336
+ }
524
1337
  };
1338
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1339
+ "section",
1340
+ {
1341
+ "aria-label": `${t("BalanceCard.title", p.locale)}: ${p.token}`,
1342
+ lang: p.locale,
1343
+ className: "flex flex-col gap-2",
1344
+ children: [
1345
+ /* @__PURE__ */ jsxRuntime.jsx(
1346
+ StatCard,
1347
+ {
1348
+ label: t("BalanceCard.title", p.locale),
1349
+ value: p.balance,
1350
+ unit: p.token,
1351
+ locale: p.locale
1352
+ }
1353
+ ),
1354
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1355
+ p.network ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: p.network, variant: "info", locale: p.locale }) : null,
1356
+ p.testnet ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: t("BalanceCard.testnet", p.locale), variant: "warning", locale: p.locale }) : null
1357
+ ] }),
1358
+ p.usdValue ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-slate-600", children: [
1359
+ t("BalanceCard.value", p.locale),
1360
+ ":",
1361
+ " ",
1362
+ formatDecimalString(p.usdValue, p.locale, p.currency)
1363
+ ] }) : null,
1364
+ p.address ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-slate-600", children: [
1365
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", title: p.address, children: truncateAddress(p.address) }),
1366
+ canCopy ? /* @__PURE__ */ jsxRuntime.jsx(
1367
+ "button",
1368
+ {
1369
+ type: "button",
1370
+ onClick: copy,
1371
+ className: "rounded border border-slate-300 px-2 py-0.5 text-xs hover:bg-slate-50",
1372
+ children: t("BalanceCard.copy", p.locale)
1373
+ }
1374
+ ) : null,
1375
+ copied ? /* @__PURE__ */ jsxRuntime.jsx("output", { children: t("BalanceCard.copied", p.locale) }) : null
1376
+ ] }) : null
1377
+ ]
1378
+ }
1379
+ );
525
1380
  }
526
- function SkeletonInner({ variant, width, height, count, animation, locale }) {
527
- const { widthCss, heightCss } = resolveSkeletonDimensions(variant, width, height);
528
- const animClass = getAnimationClass(animation);
529
- const variantClass = getVariantClass(variant);
530
- const ariaLabel = t("Skeleton.aria.label", locale);
531
- const baseClass = `${SKELETON_BG_CLASS} ${variantClass} ${animClass}`.trim();
532
- if (variant === "text") {
533
- return (
534
- // biome-ignore lint/a11y/useSemanticElements: role="status" is the correct ARIA pattern for loading indicators; <output> conveys form output semantics which is incorrect here
535
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": ariaLabel, "aria-busy": "true", className: "flex flex-col gap-2", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(
536
- "span",
537
- {
538
- className: `block ${baseClass}`,
539
- style: {
540
- width: lastLineWidth(i, count, widthCss),
541
- height: heightCss
542
- },
543
- "aria-hidden": "true"
544
- },
545
- textLineKey(i)
546
- )) })
1381
+
1382
+ // src/components/display/BalanceCard.markdown.ts
1383
+ function balanceCardToMarkdown(props, locale = "en") {
1384
+ const parsed = BalanceCardPropsSchema.safeParse(props);
1385
+ const title = mdHeading(t("BalanceCard.title", locale));
1386
+ if (!parsed.success || parsed.data.token === "" && parsed.data.balance === "") {
1387
+ return `${title}
1388
+
1389
+ ${t("BalanceCard.empty.message", locale)}
1390
+ `;
1391
+ }
1392
+ const p = parsed.data;
1393
+ const where = [p.network, p.testnet ? t("BalanceCard.testnet", locale) : ""].filter(Boolean);
1394
+ const lines = [
1395
+ `- ${mdEscape(p.token)}: ${mdEscape(p.balance)}${where.length ? ` (${mdEscape(where.join(", "))})` : ""}`
1396
+ ];
1397
+ if (p.usdValue) {
1398
+ lines.push(
1399
+ `- ${t("BalanceCard.value", locale)}: ${mdEscape(formatDecimalString(p.usdValue, locale, p.currency))}`
547
1400
  );
548
1401
  }
1402
+ if (p.address) lines.push(`- ${t("BalanceCard.address", locale)}: ${mdCode(p.address)}`);
1403
+ if (p.tokenAddress)
1404
+ lines.push(`- ${t("BalanceCard.contract", locale)}: ${mdCode(p.tokenAddress)}`);
1405
+ return `${title}
1406
+
1407
+ ${lines.join("\n")}
1408
+ `;
1409
+ }
1410
+ var VirtualListPropsSchema = zod.z.object({
1411
+ /** Array of items to render — required, can be empty */
1412
+ items: zod.z.array(zod.z.unknown()),
1413
+ /** Fixed row height in px — mutually exclusive with estimateSize */
1414
+ itemHeight: zod.z.number().positive().optional(),
1415
+ /** Variable row height estimator — mutually exclusive with itemHeight */
1416
+ estimateSize: zod.z.function().args(zod.z.number()).returns(zod.z.number()).optional(),
1417
+ /** Row renderer — required (runtime function, excluded from strict Zod parse but validated for presence) */
1418
+ renderItem: zod.z.function().args(zod.z.unknown(), zod.z.number()).returns(zod.z.unknown()).optional(),
1419
+ /** Rows to render outside visible area (default: 5) */
1420
+ overscan: zod.z.number().int().min(0).optional(),
1421
+ /** CSS class applied to the scroll container */
1422
+ className: zod.z.string().optional(),
1423
+ /** Locale for built-in empty state + error messages */
1424
+ locale: zod.z.enum(["en", "fr"]).optional(),
1425
+ /** a11y: when provided, rows become keyboard-activable (role=button + tabIndex=0 + Enter/Space) */
1426
+ onRowClick: zod.z.function().args(zod.z.unknown(), zod.z.number()).returns(zod.z.void()).optional()
1427
+ });
1428
+ function validateVirtualListProps(raw) {
1429
+ return VirtualListPropsSchema.parse(raw);
1430
+ }
1431
+ var DEFAULT_OVERSCAN = 5;
1432
+ var DEFAULT_ITEM_HEIGHT = 40;
1433
+ function VirtualList(props) {
1434
+ try {
1435
+ validateVirtualListProps({
1436
+ items: props.items,
1437
+ itemHeight: props.itemHeight,
1438
+ overscan: props.overscan,
1439
+ className: props.className,
1440
+ locale: props.locale
1441
+ });
1442
+ } catch {
1443
+ const locale2 = props.locale === "fr" ? "fr" : "en";
1444
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("VirtualList.error.invalidProps", locale2) });
1445
+ }
1446
+ const locale = props.locale === "fr" ? "fr" : "en";
1447
+ if (props.items.length === 0) {
1448
+ return /* @__PURE__ */ jsxRuntime.jsx("output", { "aria-live": "polite", children: t("VirtualList.empty.message", locale) });
1449
+ }
1450
+ return /* @__PURE__ */ jsxRuntime.jsx(
1451
+ VirtualListInner,
1452
+ {
1453
+ items: props.items,
1454
+ itemHeight: props.itemHeight,
1455
+ estimateSize: props.estimateSize,
1456
+ renderItem: props.renderItem,
1457
+ onRowClick: props.onRowClick,
1458
+ overscan: props.overscan ?? DEFAULT_OVERSCAN,
1459
+ className: props.className,
1460
+ locale
1461
+ }
1462
+ );
1463
+ }
1464
+ function VirtualListInner({
1465
+ items,
1466
+ itemHeight,
1467
+ estimateSize,
1468
+ renderItem,
1469
+ onRowClick,
1470
+ overscan,
1471
+ className
1472
+ }) {
1473
+ const scrollRef = react.useRef(null);
1474
+ const resolvedEstimateSize = react.useCallback(
1475
+ (index) => {
1476
+ if (estimateSize) return estimateSize(index);
1477
+ return itemHeight ?? DEFAULT_ITEM_HEIGHT;
1478
+ },
1479
+ [estimateSize, itemHeight]
1480
+ );
1481
+ const rowVirtualizer = reactVirtual.useVirtualizer({
1482
+ count: items.length,
1483
+ /* v8 ignore next 1 */
1484
+ getScrollElement: () => scrollRef.current,
1485
+ estimateSize: resolvedEstimateSize,
1486
+ overscan
1487
+ });
1488
+ const virtualItems = rowVirtualizer.getVirtualItems();
1489
+ const totalSize = rowVirtualizer.getTotalSize();
549
1490
  return /* @__PURE__ */ jsxRuntime.jsx(
550
1491
  "div",
551
1492
  {
552
- role: "status",
553
- "aria-label": ariaLabel,
554
- "aria-busy": "true",
555
- className: `block ${baseClass}`,
556
- style: { width: widthCss, height: heightCss }
1493
+ "data-virtual": "true",
1494
+ ref: scrollRef,
1495
+ className,
1496
+ style: { height: 400, overflow: "auto", position: "relative" },
1497
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: `${totalSize}px`, position: "relative" }, children: virtualItems.map((virtualRow) => {
1498
+ const item = items[virtualRow.index];
1499
+ const index = virtualRow.index;
1500
+ const rowProps = onRowClick ? {
1501
+ role: "button",
1502
+ tabIndex: 0,
1503
+ "data-list-row": true,
1504
+ style: {
1505
+ position: "absolute",
1506
+ top: 0,
1507
+ left: 0,
1508
+ width: "100%",
1509
+ transform: `translateY(${virtualRow.start}px)`,
1510
+ cursor: "pointer"
1511
+ },
1512
+ onClick: () => onRowClick(item, index),
1513
+ onKeyDown: (e) => {
1514
+ if (e.key === "Enter" || e.key === " ") {
1515
+ e.preventDefault();
1516
+ onRowClick(item, index);
1517
+ }
1518
+ }
1519
+ } : {
1520
+ "data-list-row": true,
1521
+ style: {
1522
+ position: "absolute",
1523
+ top: 0,
1524
+ left: 0,
1525
+ width: "100%",
1526
+ transform: `translateY(${virtualRow.start}px)`
1527
+ }
1528
+ };
1529
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...rowProps, children: renderItem(item, index) }, virtualRow.key);
1530
+ }) })
557
1531
  }
558
1532
  );
559
1533
  }
560
- function Skeleton(raw) {
1534
+
1535
+ // src/components/shared/datetime.ts
1536
+ function formatTimestamp(iso, locale, timeZone) {
1537
+ const d = new Date(iso);
1538
+ if (Number.isNaN(d.getTime())) return iso;
561
1539
  try {
562
- const props = validateSkeletonProps(raw);
563
- return /* @__PURE__ */ jsxRuntime.jsx(SkeletonInner, { ...props });
1540
+ return new Intl.DateTimeFormat(locale, {
1541
+ dateStyle: "medium",
1542
+ timeStyle: "short",
1543
+ ...timeZone ? { timeZone } : {}
1544
+ }).format(d);
564
1545
  } catch {
565
- const locale = raw.locale === "fr" ? "fr" : "en";
566
- return /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-red-600 text-sm", children: t("Skeleton.error.invalidProps", locale) });
1546
+ return iso;
1547
+ }
1548
+ }
1549
+
1550
+ // src/components/shared/url.ts
1551
+ function normalizeHttpUrl(value) {
1552
+ if (typeof value !== "string") return void 0;
1553
+ try {
1554
+ const u = new URL(value);
1555
+ return u.protocol === "https:" || u.protocol === "http:" ? u.href : void 0;
1556
+ } catch {
1557
+ return void 0;
1558
+ }
1559
+ }
1560
+ var FeedMessageSchema = zod.z.object({
1561
+ id: zod.z.string().optional(),
1562
+ sender: zod.z.string().min(1),
1563
+ channel: zod.z.string().optional(),
1564
+ /** Plain text. Rendered as text, never as HTML. */
1565
+ content: zod.z.string(),
1566
+ /** ISO 8601. */
1567
+ timestamp: zod.z.string(),
1568
+ attachmentUrl: zod.z.string().optional(),
1569
+ attachmentLabel: zod.z.string().optional(),
1570
+ unread: zod.z.boolean().optional()
1571
+ });
1572
+ var MessageFeedPropsSchema = zod.z.object({
1573
+ messages: zod.z.array(FeedMessageSchema).default([]),
1574
+ maxItems: zod.z.number().int().positive().optional(),
1575
+ /** IANA timezone for timestamps (e.g. `hostContext.timeZone`). */
1576
+ timeZone: zod.z.string().optional(),
1577
+ /** Above this many shown messages the feed is virtualised through VirtualList. */
1578
+ virtualizeThreshold: zod.z.number().int().positive().default(50),
1579
+ loading: zod.z.boolean().default(false),
1580
+ locale: zod.z.enum(["en", "fr"]).default("en")
1581
+ });
1582
+ function validateMessageFeedProps(raw) {
1583
+ return MessageFeedPropsSchema.parse(raw);
1584
+ }
1585
+ function latestMessages(messages, maxItems) {
1586
+ const shown = maxItems === void 0 ? [...messages] : messages.slice(-maxItems);
1587
+ return { shown, total: messages.length };
1588
+ }
1589
+ function Attachment({
1590
+ m,
1591
+ locale,
1592
+ onOpenLink
1593
+ }) {
1594
+ if (!m.attachmentUrl) return null;
1595
+ const label = m.attachmentLabel ?? t("MessageFeed.attachment", locale);
1596
+ const url = normalizeHttpUrl(m.attachmentUrl);
1597
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: label });
1598
+ return onOpenLink ? /* @__PURE__ */ jsxRuntime.jsx(
1599
+ "button",
1600
+ {
1601
+ type: "button",
1602
+ onClick: () => onOpenLink(url),
1603
+ className: "text-left text-sm text-blue-700 underline",
1604
+ children: label
1605
+ }
1606
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1607
+ "a",
1608
+ {
1609
+ href: url,
1610
+ rel: "noopener noreferrer",
1611
+ target: "_blank",
1612
+ className: "text-sm text-blue-700 underline",
1613
+ children: label
1614
+ }
1615
+ );
1616
+ }
1617
+ function Message({
1618
+ m,
1619
+ locale,
1620
+ timeZone,
1621
+ onOpenLink
1622
+ }) {
1623
+ return /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "flex flex-col gap-1 border-b border-slate-100 py-2", children: [
1624
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: m.sender }),
1626
+ m.channel ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: m.channel, variant: "neutral", locale }) : null,
1627
+ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: m.timestamp, className: "text-xs text-slate-500", children: formatTimestamp(m.timestamp, locale, timeZone) }),
1628
+ m.unread ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-blue-700", children: t("MessageFeed.unread", locale) }) : null
1629
+ ] }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 whitespace-pre-wrap text-sm text-slate-800", children: m.content }),
1631
+ /* @__PURE__ */ jsxRuntime.jsx(Attachment, { m, locale, onOpenLink })
1632
+ ] });
1633
+ }
1634
+ function MessageFeed(raw = {}) {
1635
+ const parsed = MessageFeedPropsSchema.safeParse(raw);
1636
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1637
+ if (!parsed.success) {
1638
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("MessageFeed.error.invalidProps", locale) });
1639
+ }
1640
+ const p = parsed.data;
1641
+ if (p.loading) {
1642
+ 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 }) });
1643
+ }
1644
+ if (p.messages.length === 0) {
1645
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("MessageFeed.empty.message", p.locale), locale: p.locale });
1646
+ }
1647
+ const { shown, total } = latestMessages(p.messages, p.maxItems);
1648
+ const virtual = shown.length > p.virtualizeThreshold;
1649
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": t("MessageFeed.title", p.locale), lang: p.locale, children: [
1650
+ 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,
1651
+ virtual ? /* @__PURE__ */ jsxRuntime.jsx(
1652
+ VirtualList,
1653
+ {
1654
+ items: shown,
1655
+ itemHeight: 96,
1656
+ locale: p.locale,
1657
+ renderItem: (m) => /* @__PURE__ */ jsxRuntime.jsx(Message, { m, locale: p.locale, timeZone: p.timeZone, onOpenLink: raw.onOpenLink })
1658
+ }
1659
+ ) : /* @__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}`)) })
1660
+ ] });
1661
+ }
1662
+
1663
+ // src/components/display/MessageFeed.markdown.ts
1664
+ function messageFeedToMarkdown(props, locale = "en") {
1665
+ const parsed = MessageFeedPropsSchema.safeParse(props);
1666
+ const title = mdHeading(t("MessageFeed.title", locale));
1667
+ if (!parsed.success || parsed.data.messages.length === 0) {
1668
+ return `${title}
1669
+
1670
+ ${t("MessageFeed.empty.message", locale)}
1671
+ `;
1672
+ }
1673
+ const { messages, maxItems, timeZone } = parsed.data;
1674
+ const { shown, total } = latestMessages(messages, maxItems);
1675
+ const lines = shown.map((m) => {
1676
+ const when = [formatTimestamp(m.timestamp, locale, timeZone), m.channel ?? ""].filter(Boolean).join(", ");
1677
+ const href = normalizeHttpUrl(m.attachmentUrl);
1678
+ const label = mdEscape(m.attachmentLabel ?? t("MessageFeed.attachment", locale));
1679
+ const attachment = m.attachmentUrl ? href ? ` [${label}](${mdUrl(href)})` : ` ${label}` : "";
1680
+ return `> **${mdEscape(m.sender)}** (${mdEscape(when)}): ${mdEscape(m.content)}${attachment}`;
1681
+ });
1682
+ const cut = shown.length < total ? `${t("MessageFeed.showing", locale).replace("{n}", String(shown.length)).replace("{total}", String(total))}
1683
+
1684
+ ` : "";
1685
+ return `${title}
1686
+
1687
+ ${cut}${lines.join("\n>\n")}
1688
+ `;
1689
+ }
1690
+
1691
+ // src/components/forms/RadioGroup.logic.ts
1692
+ function getOptionLabelClasses(appearance, isSelected) {
1693
+ const base = "mosaic-forms-radiogroup-option-label inline-flex items-center px-3 py-1 text-sm";
1694
+ const shape = "rounded-full border" ;
1695
+ const tone = isSelected ? "bg-slate-900 text-white border-slate-900" : "bg-white text-slate-700 border-slate-300 hover:bg-slate-50";
1696
+ return [base, shape, tone].filter(Boolean).join(" ");
1697
+ }
1698
+
1699
+ // src/components/display/PipelineBoard.logic.ts
1700
+ function stageSummary(deals) {
1701
+ const amounts = deals.flatMap((d) => d.amount === void 0 ? [] : [d.amount]);
1702
+ return { count: deals.length, total: sumDecimalStrings(amounts) };
1703
+ }
1704
+ var SUMMARY_STAGE_LIMIT = 5;
1705
+ var PipelineDealSchema = zod.z.object({
1706
+ id: zod.z.string().min(1),
1707
+ title: zod.z.string().min(1),
1708
+ /** Decimal STRING (never a float), in the board currency. */
1709
+ amount: zod.z.string().optional(),
1710
+ contact: zod.z.string().optional(),
1711
+ url: zod.z.string().optional()
1712
+ });
1713
+ var PipelineStageSchema = zod.z.object({
1714
+ id: zod.z.string().min(1),
1715
+ name: zod.z.string().min(1),
1716
+ deals: zod.z.array(PipelineDealSchema).default([])
1717
+ });
1718
+ var PipelineBoardPropsSchema = zod.z.object({
1719
+ stages: zod.z.array(PipelineStageSchema).default([]),
1720
+ /** ISO 4217 code used to format amounts. */
1721
+ currency: zod.z.string().default("EUR"),
1722
+ layout: zod.z.enum(["board", "summary"]).default("board"),
1723
+ loading: zod.z.boolean().default(false),
1724
+ locale: zod.z.enum(["en", "fr"]).default("en")
1725
+ });
1726
+ function validatePipelineBoardProps(raw) {
1727
+ return PipelineBoardPropsSchema.parse(raw);
1728
+ }
1729
+ function dealsLabel(n, locale) {
1730
+ return n === 1 ? t("PipelineBoard.deals.one", locale) : t("PipelineBoard.deals.many", locale).replace("{n}", String(n));
1731
+ }
1732
+ function Totals({
1733
+ stage,
1734
+ currency,
1735
+ locale
1736
+ }) {
1737
+ const { count, total } = stageSummary(stage.deals);
1738
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col text-sm text-slate-600", children: [
1739
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: dealsLabel(count, locale) }),
1740
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: total === void 0 ? "\u2014" : formatDecimalString(total, locale, currency) })
1741
+ ] });
1742
+ }
1743
+ function PipelineBoard(raw = {}) {
1744
+ const parsed = PipelineBoardPropsSchema.safeParse(raw);
1745
+ const locale = raw?.locale === "fr" ? "fr" : "en";
1746
+ const [hidden, setHidden] = react.useState(/* @__PURE__ */ new Set());
1747
+ if (!parsed.success) {
1748
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", children: t("PipelineBoard.error.invalidProps", locale) });
567
1749
  }
1750
+ const p = parsed.data;
1751
+ if (p.loading) {
1752
+ 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 }) });
1753
+ }
1754
+ if (p.stages.length === 0) {
1755
+ return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { title: t("PipelineBoard.empty.message", p.locale), locale: p.locale });
1756
+ }
1757
+ if (p.layout === "summary") {
1758
+ const shown = p.stages.slice(0, SUMMARY_STAGE_LIMIT);
1759
+ const rest = p.stages.length - shown.length;
1760
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1761
+ "section",
1762
+ {
1763
+ "aria-label": t("PipelineBoard.title", p.locale),
1764
+ lang: p.locale,
1765
+ className: "flex flex-col gap-2",
1766
+ children: [
1767
+ /* @__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: [
1768
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: s.name }),
1769
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1770
+ ] }, s.id)) }),
1771
+ rest > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500", children: t("PipelineBoard.more", p.locale).replace("{n}", String(rest)) }) : null,
1772
+ raw.onRequestFullscreen ? /* @__PURE__ */ jsxRuntime.jsx(
1773
+ "button",
1774
+ {
1775
+ type: "button",
1776
+ onClick: raw.onRequestFullscreen,
1777
+ className: "self-start rounded border border-slate-300 px-3 py-1 text-sm hover:bg-slate-50",
1778
+ children: t("PipelineBoard.summary.open", p.locale)
1779
+ }
1780
+ ) : null
1781
+ ]
1782
+ }
1783
+ );
1784
+ }
1785
+ const visible = p.stages.filter((s) => !hidden.has(s.id));
1786
+ const toggle = (id) => {
1787
+ const next = new Set(hidden);
1788
+ if (next.has(id)) next.delete(id);
1789
+ else if (visible.length > 1) next.add(id);
1790
+ setHidden(next);
1791
+ };
1792
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1793
+ "section",
1794
+ {
1795
+ "aria-label": t("PipelineBoard.title", p.locale),
1796
+ lang: p.locale,
1797
+ className: "flex flex-col gap-3",
1798
+ children: [
1799
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "m-0 flex min-w-0 flex-wrap gap-2 border-0 p-0", children: [
1800
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "sr-only", children: t("PipelineBoard.filter.label", p.locale) }),
1801
+ p.stages.map((s) => {
1802
+ const on = !hidden.has(s.id);
1803
+ return /* @__PURE__ */ jsxRuntime.jsx(
1804
+ "button",
1805
+ {
1806
+ type: "button",
1807
+ "aria-pressed": on,
1808
+ onClick: () => toggle(s.id),
1809
+ className: getOptionLabelClasses("chips", on),
1810
+ children: s.name
1811
+ },
1812
+ s.id
1813
+ );
1814
+ })
1815
+ ] }),
1816
+ /* @__PURE__ */ jsxRuntime.jsx(
1817
+ "div",
1818
+ {
1819
+ className: "flex flex-row gap-3 overflow-x-auto",
1820
+ style: {
1821
+ scrollPaddingLeft: "var(--mosaic-safe-area-left, 0px)",
1822
+ scrollPaddingRight: "var(--mosaic-safe-area-right, 0px)"
1823
+ },
1824
+ children: visible.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
1825
+ "section",
1826
+ {
1827
+ "aria-label": s.name,
1828
+ className: "flex w-64 shrink-0 flex-col gap-2 rounded-lg border border-slate-200 bg-slate-50 p-3",
1829
+ children: [
1830
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start justify-between gap-2", children: [
1831
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-slate-900", children: s.name }),
1832
+ /* @__PURE__ */ jsxRuntime.jsx(Totals, { stage: s, currency: p.currency, locale: p.locale })
1833
+ ] }),
1834
+ 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: [
1835
+ raw.onOpenLink && normalizeHttpUrl(d.url) ? /* @__PURE__ */ jsxRuntime.jsx(
1836
+ "button",
1837
+ {
1838
+ type: "button",
1839
+ onClick: () => raw.onOpenLink?.(normalizeHttpUrl(d.url)),
1840
+ className: "text-left font-medium text-blue-700 underline",
1841
+ children: d.title
1842
+ }
1843
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-slate-900", children: d.title }),
1844
+ 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,
1845
+ d.contact ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { label: d.contact, variant: "neutral", locale: p.locale }) : null
1846
+ ] }, d.id)) })
1847
+ ]
1848
+ },
1849
+ s.id
1850
+ ))
1851
+ }
1852
+ )
1853
+ ]
1854
+ }
1855
+ );
1856
+ }
1857
+
1858
+ // src/components/display/PipelineBoard.markdown.ts
1859
+ function pipelineBoardToMarkdown(props, locale = "en") {
1860
+ const parsed = PipelineBoardPropsSchema.safeParse(props);
1861
+ const title = mdHeading(t("PipelineBoard.title", locale));
1862
+ if (!parsed.success || parsed.data.stages.length === 0) {
1863
+ return `${title}
1864
+
1865
+ ${t("PipelineBoard.empty.message", locale)}
1866
+ `;
1867
+ }
1868
+ const { stages, currency } = parsed.data;
1869
+ const money = (v) => mdEscape(formatDecimalString(v, locale, currency));
1870
+ const table = mdTable(
1871
+ [
1872
+ t("PipelineBoard.col.stage", locale),
1873
+ t("PipelineBoard.col.deals", locale),
1874
+ t("PipelineBoard.total", locale)
1875
+ ],
1876
+ stages.map((s) => {
1877
+ const sum = stageSummary(s.deals);
1878
+ return [
1879
+ mdEscape(s.name),
1880
+ String(sum.count),
1881
+ sum.total === void 0 ? "-" : money(sum.total)
1882
+ ];
1883
+ })
1884
+ );
1885
+ const sections = stages.filter((s) => s.deals.length > 0).map((s) => {
1886
+ const lines = s.deals.map((d) => {
1887
+ const amount = d.amount === void 0 ? "" : ` (${money(d.amount)})`;
1888
+ const contact = d.contact ? ` - ${mdEscape(d.contact)}` : "";
1889
+ return `- ${mdEscape(d.title)}${amount}${contact}`;
1890
+ });
1891
+ return `${mdHeading(mdEscape(s.name), 2)}
1892
+
1893
+ ${lines.join("\n")}`;
1894
+ });
1895
+ return `${title}
1896
+
1897
+ ${table}
1898
+ ${sections.length ? `
1899
+ ${sections.join("\n\n")}
1900
+ ` : ""}`;
568
1901
  }
569
1902
 
1903
+ exports.BalanceCard = BalanceCard;
1904
+ exports.BalanceCardPropsSchema = BalanceCardPropsSchema;
570
1905
  exports.EmptyStatePropsSchema = EmptyStatePropsSchema;
1906
+ exports.MessageFeed = MessageFeed;
1907
+ exports.MessageFeedPropsSchema = MessageFeedPropsSchema;
1908
+ exports.PipelineBoard = PipelineBoard;
1909
+ exports.PipelineBoardPropsSchema = PipelineBoardPropsSchema;
571
1910
  exports.Skeleton = Skeleton;
572
1911
  exports.SkeletonPropsSchema = SkeletonPropsSchema;
1912
+ exports.StatCard = StatCard;
1913
+ exports.StatCardPropsSchema = StatCardPropsSchema;
573
1914
  exports.StreamingTableView = StreamingTableView;
574
1915
  exports.TableView = TableView;
575
1916
  exports.TableViewPropsSchema = TableViewPropsSchema;
1917
+ exports.balanceCardToMarkdown = balanceCardToMarkdown;
1918
+ exports.compareDecimalStrings = compareDecimalStrings;
1919
+ exports.messageFeedToMarkdown = messageFeedToMarkdown;
1920
+ exports.nextSort = nextSort;
1921
+ exports.pipelineBoardToMarkdown = pipelineBoardToMarkdown;
576
1922
  exports.resolveSkeletonDimensions = resolveSkeletonDimensions;
1923
+ exports.sortItems = sortItems;
1924
+ exports.statCardToMarkdown = statCardToMarkdown;
1925
+ exports.validateBalanceCardProps = validateBalanceCardProps;
577
1926
  exports.validateEmptyStateProps = validateEmptyStateProps;
1927
+ exports.validateMessageFeedProps = validateMessageFeedProps;
1928
+ exports.validatePipelineBoardProps = validatePipelineBoardProps;
578
1929
  exports.validateSkeletonProps = validateSkeletonProps;
1930
+ exports.validateStatCardProps = validateStatCardProps;
579
1931
  exports.validateTableViewProps = validateTableViewProps;
580
1932
  //# sourceMappingURL=display.cjs.map
581
1933
  //# sourceMappingURL=display.cjs.map