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