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